[C/C++] 템플릿 메소드 패턴 (Template Method Pattern)

할랑말랑·2026년 3월 26일

C/C++

목록 보기
40/45

템플릿 메소드 패턴 (template method pattern)

상위 클래스에서 알고리즘의 구조를 정의하고, 하위 클래스에서 구체적인 구현을 제공하는 방식입니다. 이 패턴은 코드의 중복을 줄이고, 알고리즘의 일관성을 유지하는 데 도움을 준다.

1. 구성 요소

  • AbstractClass (부모 - AMonster) : 템플릿 메소드를 구현하고, 자식이 구현할 추상 메소드들을 선언
  • Template Method (템플릿 메소드 - ExecuteAttack) : 알고리즘의 골격을 정의하는 실행 메소드, 내부에서 추상 메소드들을 호출
  • ConcreteClass (자식 - Goblin, Slime): 부모가 비워둔 세부 메소드(LightAttack 등)를 실제로 구현

2. 장점

  • 코드 재사용 : 알고리즘의 구조를 공유하여 코드 중복을 줄인다.
  • 유연성 : 하위 클래스에서 알고리즘의 특정 단계를 쉽게 변경할 수 있다.
  • 일관성 : 알고리즘의 전체 흐름이 상위 클래스에서 정의되므로 일관성을 유지

3. 단점

  • 복잡성 증가 : 상속 구조가 복잡해질 수 있으며, 하위 클래스가 많아질 경우 관리가 어려워질 수 있다.
  • 유연성 제한 : 메서드 호출 순서가 고정되어 있어, 완전히 다른 알고리즘을 구현하기 어렵다.

Template Method Pattern

  • 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은 자신의 체력에 따라 공격을 결정

0개의 댓글