상위 클래스에서 알고리즘의 구조를 정의하고, 하위 클래스에서 구체적인 구현을 제공하는 방식입니다. 이 패턴은 코드의 중복을 줄이고, 알고리즘의 일관성을 유지하는 데 도움을 준다.
- AbstractClass : AMonster
- ConcreteClass : Slime, Goblin
#include <iostream> #include <string> #include <random> // C++11 스타일의 난수 생성기 std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<int> dis(1, 3); // 몬스터의 공격 패턴을 나타내는 열거형 클래스 enum class MonsterAttackPattern { None, LightAttack, MediumAttack, HeavyAttack }; // 몬스터의 기본 뼈대를 정의하는 추상 클래스 (Abstract Class) class AMonster { protected: std::string name; int health; int attack; public: // 생성자: 이름과 레벨을 받아 능력치를 설정 AMonster(const std::string& _name, int _level = 1) : attack(_level * 5), health(_level * 20), name("Lv" + std::to_string(_level) + " " + _name) {} virtual ~AMonster() = default; // 가상 소멸자 // Getter 함수들 std::string GetName() const { return name; } int GetHealth() const { return health; } int GetAttack() const { return attack; } // 데미지를 받는 함수 void TakeDamage(int _damage) { if (health - _damage > 0) { health -= _damage; std::cout << name << "이(가) " << _damage << "의 피해를 받았습니다." << std::endl; } else { health = 0; std::cout << name << "이(가) " << _damage << "의 피해를 받아 사망했습니다." << std::endl; } } // 템플릿 메서드: 공격 알고리즘의 뼈대를 정의 void ExecuteAttack() { // 1. 어떤 공격을 할지 결정 (자식 클래스가 구체화) switch (AttackCheck()) { case MonsterAttackPattern::LightAttack: // 2. 결정된 공격 실행 (자식 클래스가 구체화) LightAttack(); break; case MonsterAttackPattern::MediumAttack: MediumAttack(); break; case MonsterAttackPattern::HeavyAttack: HeavyAttack(); break; } } // 자식 클래스가 반드시 재정의해야 하는 순수 가상 함수들 virtual void LightAttack() = 0; virtual void MediumAttack() = 0; virtual void HeavyAttack() = 0; virtual MonsterAttackPattern AttackCheck() = 0; }; // AMonster를 상속받는 구체적인 몬스터 클래스: 고블린 class Goblin : public AMonster { public: Goblin(const std::string& _name, int _level = 1) : AMonster(_name, _level) {} void LightAttack() override { int damage = attack / 3; for (int i = 0; i < 2; i++) { std::cout << name << "이(가) 할퀴기 공격 - 데미지 : " << damage << std::endl; } } void MediumAttack() override { int damage = attack * 1.5; std::cout << name << "이(가) 나무막대기 공격 - 데미지 : " << damage << std::endl; } void HeavyAttack() override { int damage = attack * 3; std::cout << name << "이(가) 나무막대기로 내려찍기 공격 - 데미지 : " << damage << std::endl; } // 고블린의 공격 결정 방식: 1~3 사이의 난수를 발생시켜 랜덤으로 공격 MonsterAttackPattern AttackCheck() override { int random = dis(gen); switch (random) { case 1: return MonsterAttackPattern::LightAttack; case 2: return MonsterAttackPattern::MediumAttack; case 3: return MonsterAttackPattern::HeavyAttack; } return MonsterAttackPattern::None; } }; // AMonster를 상속받는 구체적인 몬스터 클래스: 슬라임 class Slime : public AMonster { public: Slime(const std::string& _name, int _level = 1) : AMonster(_name, _level) {} void LightAttack() override { int damage = attack / 5; for (int i = 0; i < 3; i++) { std::cout << name << "이(가) 몸통 박치기 공격 - 데미지 : " << damage << std::endl; } } void MediumAttack() override { int damage = attack * 2.5; std::cout << name << "이(가) 산성 용액 발사 공격 - 데미지 : " << damage << std::endl; } void HeavyAttack() override { int damage = attack * 2.5; for (int i = 0; i < 2; i++) { std::cout << name << "이(가) 엄청난 점프 공격 - 데미지 : " << damage << std::endl; } } // 슬라임의 공격 결정 방식: 자신의 체력(health)에 따라 공격 패턴을 결정 MonsterAttackPattern AttackCheck() override { if (health > 200) { return MonsterAttackPattern::LightAttack; } else if (health > 100) { return MonsterAttackPattern::MediumAttack; } else { return MonsterAttackPattern::HeavyAttack; } return MonsterAttackPattern::None; } }; int main() { Slime s("슬라임", 10); s.ExecuteAttack(); return 0; }
AMonster::ExecuteAttack() : 이 함수가 바로 템플릿 메서드
AttackCheck()를 호출해서 어떤 공격을 할지 정하고, 그 결과에 따라 LightAttack(), MediumAttack() 등을 호출하는 전체적인 흐름(알고리즘)을 정의AttackCheck() : Goblin은 랜덤으로 공격을 결정, Slime은 자신의 체력에 따라 공격을 결정