본문 바로가기

Computer Science/Design Pattern

[게임 프로그래밍 패턴] 13. 컴포넌트 패턴

컴포넌트 패턴 (Component Pattern)

259 : 한 개체가 여러 분야를 서로 커플링 없이 다룰 수 있게 한다.

컴포넌트 패턴

여러 분야를 다루는 하나의 개체가 있다. 분야 별로 각각의 코드를 별도의 컴포넌트 클래스에 둔다.
개체 클래스는 단순히 이들 컴포넌트들의 컨테이너 역할만 한다.

언제 쓸 것인가?

  1. 한 클래스에서 여러 분야를 건드리고 있어서, 이들을 서로 디커플링하고 싶다.
  2. 클래스가 거대해져서 작업하기가 어렵다.
  3. 여러 다른 기능을 공유하는 다양한 객체를 정의하고 싶다. 상속으로는 원하는 부분만 골라서 재사용할 수가 없다.

주의사항

컴포넌트끼리 통신하기도 더 어렵고, 컴포넌트들을 메모리 어디에 둘지 제어하는 것도 더 복잡하다. 코드베이스 규모가 크면 이런 복잡성에서 오는 손해보다 디커플링과 컴포넌트 재사용에서 얻는 이득이 더 클 수 있다. 하지만 컴포넌트 패턴을 적용하기 전에 아직 있지도 않은 문제에 대한 '해결책'을 오버엔지니어링하려는 것은 아닌지 주의해야 한다.

예제코드

public class User {
    int velocity;
    int x, y;

    private InputComponent input_;
    private GraphicsComponent graphics_;
    private PhysicsComponent physics_;

    public void update(World world, Graphics graphics) {
        input_.update(this);
        graphics_.update(this, graphics);
        physics_.update(this, world);
    }
}
public class InputComponent {
    private static final int WALK_ACCELERATION = 1;
    public void update(User user) {
        switch (Controller::getJoysickDirection()) {
            case DIR_LEFT:
                user.velocity -= WALK_ACCELERATION;
                break;
            case DIR_RIGHT:
                user.velocity += WALK_ACCELERATION;
                break;
        }
    }
}
public class PhysicsComponent {
    private Volume volume;
    public void update(User user, World world) {
        user.x += user.velocity;
        world.resolveCollision(volume, user.x, user.y, user.velocity);
    }
}
public class GraphicsComponent {
    private Sprite spriteStand;
    private Sprite spriteWalkLeft;
    private Sprite spriteWalkRight;

    public void update(User user, Graphics graphics) {
        Sprite sprite = spriteStand;
        if (user.velocity < 0) {
            sprite = spriteWalkLeft;
        } else if (user.velocity > 0) {
            sprite = spriteWalkRight;
        }
        graphics.draw(sprite, user.x, user.y);
    }
}

디자인 결정

  1. 객체는 컴포넌트를 어떻게 얻는가?
    1. 객체가 필요한 컴포넌트를 생성
      - 객체는 항상 필요한 컴포넌트를 가지게 된다.
      - 객체를 변경하기 어렵다.
    2. 외부 코드에서 컴포넌트 제공
      - 객체가 훨씬 유연해진다.
      - 객체를 구체 컴포넌트 자료형으로부터 디커플링할 수 있다.
  2. 컴포넌트끼리는 어떻게 통신할 것인가?
    1. 컨테이너 객체의 상태를 변경
      - 컴포넌트들은 서로 디커플링 상태를 유지한다.
      - 컴포넌트들이 공유하는 정보를 컨테이너 객체에 전부 넣어야 한다.
      - 컴포넌트끼리 암시적으로 통신하다보니 컴포넌트 실행 순서에 의존하게 된다.
    2. 컴포넌트가 서로 참조
      - 간단하고 빠르다
      - 두 컴포넌트가 강하게 결합된다.
    3. 메시지 전달
      - 하위 컴포넌트들은 디커플링된다.
      - 컨테이너 객체는 단순하다.