스타크래프트로 알아보는 객체지향 프로그래밍의 4가지 특징

민씨·2023년 11월 21일
0
post-thumbnail
post-custom-banner

Jacked up and good to go.

개요

객체지향 프로그래밍은 4가지의 특징을 가지고 있습니다.

  • 추상화(Abstraction)
  • 상속(Inheritance)
  • 다형성(Polymorphism)
  • 캡슐화(Encapsulation)

이러한 특징들은 자바 기본 서적에서 반드시 언급되며, 객체지향 언어로 개발을 경험한 분들은 이미 잘 알고 계실 것입니다.

저는 학부생 시절, 이러한 개념들을 이해하는 것이 정말 어려웠습니다. 공부를 하려고 검색을 해보면 아래와 같은 내용이 많이 나오곤 했습니다. 이해는 가지만 와닿지는 않았죠.

추상화

  • 자신이 원하는 특성만 취하고 필요없는 부분을 추려 핵심만 표현하는 행위
  • 불필요한 특성을 배제하고 포괄적인 의미를 지니게 한다.

상속

  • 하위 클래스가 상위 클래스의 상태와 행동을 물려받는 것
  • 하위 클래스는 필요에 따라 확장하거나 상위 클래스의 행동을 재정의 할 수 있다.

다형성

  • 서로 다른 유형의 객체가 동일한 메시지에 서로 다르게 반응하는 것

캡슐화

  • 상태를 캡슐안에 감춰둔 채 외부로 노출하지 않는 것
  • 다른 객체는 무엇을 수행하는 지는 알 수 있지만, 어떻게 수행하는 지 알 수 없다.

그래서 아래처럼 생각하고 넘어갔습니다.

  • 추상화? 인터페이스와 관련된 내용 아닌가요?
  • 상속? 클래스의 상속, 인터페이스의 구현에 해당하는 내용 아닌가요?
  • 다형성? Overloading, Overriding 관련된 내용 아닌가요?
  • 캡슐화? getter(), setter() 메서드 아닌가요?

구체적인 예시가 없으면 특정 정보를 받아들이기가 참 어렵습니다.

그래서 "추상화는 A다", "상속은 B다"와 같은 설명이 아닌 스타크래프트 세상에 존재하는 민씨가 팩토리에서 탱크를 생산하는 시나리오를 통해 객체지향 프로그래밍의 4가지 특징을 알아보려합니다.

시나리오

스타크래프트의 세계에서는 팩토리에서 버튼만 누르면 자판기에서 음료수 뽑듯이 탱크도 뽑아줍니다.

순식간에 탱크를 뽑아주는 이 기계는 유닛을 생산하는 세 가지의 버튼이 있습니다.

  • (V) 버튼을 누르면 벌처가 생산됩니다.
  • (T) 버튼을 누르면 탱크가 생산됩니다.
  • (G) 버튼을 누르면 골리앗이 생산됩니다.

이 엄청난 기계는 "짐 레이너"가 만든 "레이너 팩토리"와 "사라 케리건"이 만든 "케리건 팩토리"가 존재합니다.

레이너 팩토리

레이너 팩토리는 경량화 작업을 했기에 속도가 다른 탱크보다 빠릅니다. (T) 버튼을 누르면 Jim Raynor 라는 스티커가 붙은 탱크가 생산됩니다.

케리건 팩토리

케리건 팩토리는 경량화 작업을 하지 않은 순수한 탱크만을 생산합니다. (T) 버튼을 누르면 Ghost 라는 스티커가 붙은 탱크가 생산됩니다.

근처를 지나가던 민씨는 오늘따라 탱크가 가지고 싶어졌습니다.

  • 레이너 팩토리 앞으로 가서 (T) 버튼을 눌렀더니 탱크가 생산되었습니다.
  • 케리건 팩토리 앞으로 가서 (T) 버튼을 눌렀더니 탱크가 생산되었습니다.

시나리오로 알아보는 4가지 특징

캡슐화, 추상화

팩토리에서 탱크를 생성하는 과정은 금속, 티타늄 등의 자원을 확보하고 이를 이용하여 부품을 만들고 조립한 뒤 완성된 탱크를 테스트까지 하는 매우 기술적이고 복잡한 작업입니다.

하지만 민씨는 복잡한 작업을 알 필요 없이 (T) 버튼으로 손쉽게 탱크를 생산할 수 있습니다.

즉 민씨는 팩토리가 탱크를 어떻게 생산하는 지는 알 필요가 없습니다.

이는 캡슐화에 해당합니다.

또한 팩토리는 민씨가 탱크를 생산하는 데 필요한 최소한의 정보 (T) 버튼만을 제공합니다.

즉 팩토리가 추상화를 통해 복잡한 프로세스 과정을 숨기고 있습니다.

이는 추상화에 해당합니다.

상속

팩토리의 핵심 기능은 (V), (T), (G) 버튼에 해당하는 유닛의 생산에 있습니다.

따라서 레이너 팩토리와 케리건 팩토리는 이 핵심 기능을 반드시 구현해야합니다.

이는 상속에 해당합니다.

다형성

민씨는 (T) 버튼을 눌러서 탱크를 뽑았습니다.

레이너 팩토리에서는 Jim Raynor 스티커가 붙은 이동 속도가 2배인 경량화 작업이 완료된 탱크가 생산됐습니다.

케리건 팩토리에서는 Ghost 스티커가 붙은 순수한 탱크가 생산됐습니다.

민씨는 (T) 버튼을 누르는 동일한 행동을 했지만 세부 구현이 다른 두 종류의 탱크를 생산할 수 있습니다.

이는 다형성에 해당합니다.

코드로 알아보는 4가지 특징

위 시나리오를 바탕으로 한 코드를 통해 4가지 객체지향 프로그래밍 특징을 자세히 알아보겠습니다.

코드는 GitHub에서 확인하실 수 있습니다.

추상화

팩토리에는 벌처, 탱크, 골리앗의 세 유닛이 존재합니다.

Unit 클래스에서는 위 세 가지 유닛의 공통 속성을 추상화했습니다.

public class Unit {

    private int offensePower; // 공격력
    private int attackCycle; // 공격 주기
    private double speed; // 이동 속도
    private DamageType damageType; // 피해 유형
    private int range; // 사거리
}

Factory 인터페이스는 팩토리에 필요한 핵심 기능을 추상화했습니다.

public interface Factory {

    Vulture createVulture(); // 벌쳐 생산

    Tank createTank(); // 탱크 생산

    Goliath createGoliath(); // 골리앗 생산

}

Tank 인터페이스는 탱크가 가지는 기능을 추상화했습니다.

public interface Tank {

    void siegeMode(); // 시즈 모드

    void tankMode(); // 일반 모드

    double getSpeed(); // 이동속도 반환
}

상속

RaynorTank, KerriganTankTank 인터페이스를 구현하고 Unit 클래스를 상속받았습니다.

public class RaynorTank extends Unit implements Tank {
    // 구현 생략
}
public class KerriganTank extends Unit implements Tank {
    // 구현 생략
}

RaynorFactory, KerriganFactoryFactory 인터페이스를 구현하였습니다.

public class RaynorFactory implements Factory {
    // 구현 생략
}
public class KerriganTank extends Unit implements Tank {
    // 구현 생략
}

캡슐화

RaynorFactory에서 RaynorTank를 생성할 때 내부에서 어떤 작업을 수행하는 지는 외부에 노출되지 않습니다.

public class RaynorFactory implements Factory {
    @Override
    public Tank createTank() {
        RaynorTank tank = new RaynorTank();
        tank.performLightweightOperation(); // 경량화 작업
        return tank;
    }
}

따라서 User 클래스가 탱크를 생산하기 위해 할 수 있는 일은 createTank() 메서드를 호출하는 일 밖에 없습니다.

public class User {
    public Tank buttonT() {
        return this.factory.createTank();
    }
}

다형성

유저 객체가 탱크를 얻는 방법은 buttonT() 메서드를 호출하는 것입니다.

class UserFactoryIntegrationTests {

    @Test
    void buttonT() {
        // given
        User user1 = new User(new RaynorFactory());
        User user2 = new User(new KerriganFactory());

        // when
        Tank raynorTank = user1.buttonT();
        Tank kerriganTank = user2.buttonT();

        // then
        assertThat(raynorTank.getSpeed()).isNotEqualTo(kerriganTank.getSpeed());
        assertThat(raynorTank.getSpeed()).isEqualTo(kerriganTank.getSpeed() * 2);
    }

}

kerriganTank 객체는 기본적인 이동 속도의 탱크를 생산합니다.

반면에 raynorTank 객체는 경량화 작업이 적용된 이동 속도가 2배 빠른 탱크를 생산합니다.

동일한 메서드 호출에도 다른 결과를 얻을 수 있습니다.

기대효과

그래서 이렇게 만들면 뭐가 좋을까요?

재사용성 증가

위 예시로는 탱크만 생산하고 있습니다. 더 나아가서 벌처를 생산하고 싶을 땐 어떻게 하면 될까요?

public interface Vulture {

    void useSpiderMine();

    void activateIonThruster();

}
public class RaynorVulture extends Unit implements Vulture { }

탱크와 마찬가지로 벌처의 기능에 대한 인터페이스를 생성한 뒤 Unit 클래스를 상속받으면 됩니다.

유닛에 대한 공통적인 부분은 Unit 클래스에 있으므로 코드의 재사용성이 증가합니다.

위 시나리오에서 신생 회사 OtherFactory 가 등장하면 어떻게 될까요?

public class OtherFactory implements Factory {
    @Override
    public Vulture createVulture() { ... }

    @Override
    public Tank createTank() { ... }
}

레이너, 케리건과 같이 Factory 인터페이스를 구현하면 됩니다.

확장성 증가

위의 세계관에서 전쟁이 일어나 조금 더 빠른 속력의 레이너 탱크가 필요해졌습니다. 그러면 어떻게 하면 될까요?

public class RaynorTank extends Unit implements Tank {
    public void performLightweightOperation() {
        // this.setSpeed(this.getSpeed() * 2);
        this.setSpeed(this.getSpeed() * 3);
    }
}

RaynorTank 의 경량화 메서드만 수정해주면 다른 코드를 건드리지 않아도 됩니다.

또한, 공격력이 높고 사거리가 긴 새로운 탱크가 필요하다면 어떨까요?

public class OtherTank extends Unit implements Tank {

    @Override
    public void siegeMode() {
        // 공격력 사거리가 길게 구현
    }

    @Override
    public void tankMode() {
        this.init();
    }

    public void init() {
        // 공격력 사거리가 길게 구현
    }
}

Unit 클래스를 상속하고 Tank 인터페이스를 구현한 OtherTank 를 만들면 됩니다.

이렇게 된다면 User 코드의 변경 없이 buttonT() 를 이용하여 공격력이 높고, 사거리가 긴 탱크를 얻을 수 있게 됩니다.

User user = new User(new OtherFactory());

가독성 향상 및 유지보수 용이

위 확장성 증가에서 알아본 것 처럼, 경량화 작업을 수정하려면 해당 클래스의 메서드만 확인하면 됩니다.

이러한 모듈화된 구조는 코드의 가독성을 향상시키고 유지보수를 더욱 쉽게 만듭니다.

변경이 필요한 부분을 찾기 쉽고, 새로운 기능을 추가하거나 수정할 때 개발자가 알기가 쉬워지는 셈이죠.

마치며

제가 생각하는 객체지향 프로그래밍의 핵심은 유연하고 확장가능한 재사용성 높은 소프트웨어를 만들어내는 것입니다.

기대효과를 보면 알 수 있듯이 객체지향의 4가지 특징을 잘 활용한다면 좋은 소프트웨어 설계가 가능해지는데요.

하지만 너무나도 추상적인 개념이라 학습이 어렵고 실제로 적용하는 것은 까다로운게 사실입니다.

그래도 지금까지 본 스타크래프트 세계의 가상의 시나리오와 이를 코드로 구현하는 과정을 보며 조금이나마 도움이 되셨으면 좋겠습니다.

감사합니다.

profile
進取
post-custom-banner

0개의 댓글