모던 c++ 디자인 패턴 을 읽고 인상 깊었던 내용을 정리합니다.
어떤 컴퓨터 게임에 크리처들이 있다고 하자. 크리처들은 공격력과 방어력 두 가지 값을 속성으로 가진다.
크리처의 속성은 특정 이벤트가 발생 했을때 이벤트에 맞게 변경되어야 한다. 이러한 처리를 CreatureModifier를 통해서 구현한다고 하자.
struct Creature
{
string name;
int attack, defense;
...
};
class CreatureModifier
{
CreatureModifier* next{nullptr};
protected;
Creature& creature;
public:
explicit CreatureModifier(Creature& creature) : creature(creature) {]
void add(CreatureModifier* cm)
{
if (next) next->add(cm);
else next = cm;
}
virtual void handle()
{
if (next) next->handle();
}
}
CreatureModifier을 구현하여 실질적인 작업들이 추가되기 시작하면 이 구현의 의미가 더 명확 해진다.
class DoubleAttackModifier : public CreatureModifier
{
public:
explicit DoubleAttackModifier(Creatrue& creature) : CreatureModifier(creature) {}
void handle() override
{
creature.attack *= 2;
CreatureModifier::handle(); //변경 작업의 사슬이 연이어질 수 있도록 하기 위함이다.
}
};
만약 크리처가 어떤 마법의 적용을 받아 상태의 변경이 없도록 해야 한다는 요구사항이 있다면 다음처럼 구현하면 된다.
그리고 책임 사슬의 제일 앞에 NoBonusesModifier을 위치시키면 된다.
class NoBonusesModifier : public CreatureModifier
{
public:
explicit NoBonusesModifier(Creatrue& creature)
void handle() override
{
// 아무것도 안하기, 부모 클래스의 handle()을 호출하지 않는다.
}
};
서로 직접적으로 참조하지 않더라도 커뮤니케이션을 할 수 있게 한다는것을 기본 아이디어로 한다.
struct EventData
{
virtual ~EventData() = default;
virtual void print() const = 0;
};
struct PlayerScoredData : EventData
{
string player_name;
int goals_scored_so_far;
PlayerScoredData(const string& player_name, const int goals_scored_so_far) :player_name(player_name), goals_scored_so_far(goals_scored_so_far) {}
void print() const override
{
cout << player_name << " has scored!" (their " << goals_scored_so_far << " goal)" << "\n";
}
};
struct Game //매개자, 이벤트 기반 구조에서는 매개자가 직접 일을 수행하지 않는다.
{
signal<void(EventData*). events; // 관찰자
};
struct Player
{
string name;
int goals_scored = 0;
Game& game;
Player(const string& name, Game& game) : name(name), game(game) {]
void score()
{
goals_scored++;
PlayerScoredData ps{name, goals_scored};
game.events(&ps);
}
};
struct Coach
{
Game& game;
explicit Coach(Game& game) : game(game)
{
game.events.connect([](EventData* e)
{
PlayerScoredData* ps = dynamic_cast<PlayerScoredData*>(e);
if (ps && ps->goals_scored_so_far < 3)
{
cout << "coach says: well done, " << ps->player_name << "\n";
}
}
};