본문 바로가기

study/design pattern

[디자인패턴][행위패턴] 템플릿 메서드 - C++

728x90

[모던 C++ 디자인 패턴] 책을 바탕으로 공부하는 내용을 정리한 내용이다.


Template Method

전략 패턴과 매우 유사하다. 차이점은 전략 패턴이 컴포지션을 이용하는 데 반해 템플릿 메서드 패턴은 상속을 이용한다는 것이다.

하지만 핵심 원리는 어떤 알고리즘의 골격을 한 곳에 정의해 두고 상세 구현을 다른 곳에 둔다는 점에서 동일하다. 이 부분은 시스템 확정 측면에서 OCP 원칙을 준수하는 것이다.


게임 시뮬레이션

보드 게임을 예로 들어 아래와 같이 알고리즘을 정의해보자. 게임 실행 시에는 run() 메서드는 다른 메서드를 호출할 뿐이다. 그 메서드들은 퓨어 버추얼이면서 protected이기 땜누에 다른 쪽에서 호출할 수는 없다.

class Game {
private:
    void run() {
        start();
        while ( !have_winner() ) {
            take_turn();
        }
        cout << "Player " << get_winner() << " wins.\n";
    }

protected:
    virtual void start() = 0;
    virtual bool have_winner() = 0;
    virtual void take_turn() = 0;
    virtual int get_winner() = 0;
};

꼭 퓨어 버추얼일 필요는 없다. 특히 리턴값이 void인 경우는 더욱 그렇다.

전략 디자인 패턴에서는 의도적으로 아무것도 하지 않는 버추얼 메서드를 만들었다. 하지만 템플릿 메서드를 사용할 때는 문제 여부가 불분명하다.

위 코드에 공통 기능을 추가해보자. 플레이어 수와 현재 플레이어 인덱스는 모든 게임에서 의미가 있을 것이다.

class Game {
public:
    explicit Game(int number_of_players) : number_of_players(number_of_players) {}
    
    // 다른 멤버 생략
protected:
    int current_player {0};
    int number_of_players;
};

Game을 확장해 Chess를 구현해보자.

class Chess : public Game {
public:
    explicit Chess() : Game(2) {}
    
protected:
    void start() override()
    
    bool have_winner() override() {
        return turns == max_turns;
    }
    
    void take_turn() override {
        turns++;
        current_player = (current_player + 1) % number_of_players;
    }
    
    int get_winner() override {
        return current_player;
    }
    
private:
    int tunrs {0};
    int max_turns {0};
};

요약

전략 패턴에서는 컴포지션을 이용해 동적 전략과 정적 전략으로 나뉘지만 템플릿 메서드는 상속을 사용하기 때문에 정적인 방법만 존재한다. 이미 생성된 객체의 부모를 바꿀 방법은 없으므로 상속을 이용하면 결과적으로 정적인 방법이 된다.

템플릿 메서드에서는 설계 차원의 선택 사항이 한 가지뿐이다. 템플릿 메서드에서 이용될 메서드를 퓨어 버추얼로 할 것이냐 실 구현체로 할 것이냐를 선택해야 한다. 자식 클래스에서 모든 메서드를 구현할 필요가 없으면 실 구현체를 선택하고 아무것도 하지 않는 공백 함수를 두는 것이 더 편리할 수 있다.

728x90