디자인 패턴 - 파사드 패턴

정완훈·2025년 3월 20일

파사드(Facade) 패턴 in C++

강의는 C로 받았기에 AI에게 던져주고 C++로 전환했다.

파사드 패턴이란?

파사드 패턴은 구조적 디자인 패턴 중 하나로, 복잡한 서브시스템들을 하나의 단순한 인터페이스로 감싸서 클라이언트가 쉽게 사용할 수 있게 함. C++에서는 보통 객체 구성을 사용해서 파사드 클래스가 서브시스템들의 인스턴스를 멤버로 가지고, 클라이언트의 요청을 받아 적절한 서브시스템에 위임하는 방식으로 구현한다.

게임에서 전투 시스템 : 공격, 데미지 계산, 애니메이션, 사운드 같은 여러 서브시스템이 얽혀 있는데, 파사드가 “전투 턴 시작”이라는 하나의 메서드로 이 모든 걸 처리해준다. 클라이언트는 내부가 어떻게 돌아가는지 신경 쓸 필요가 없다.


왜 필요?

C++로 대규모 프로젝트를 하다 보면 클래스와 메서드가 많아져서 코드가 복잡해진다. 클라이언트가 모든 서브시스템을 직접 호출하면 코드가 지저분해지고, 나중에 서브시스템이 바뀌면 클라이언트 코드도 같이 수정해야 한다.
파사드를 쓰면 클라이언트는 파사드 하나만 알면 되고, 내부 서브시스템이 바뀌어도 파사드 안에서 처리하면 되니까 유지보수가 쉬워진다. 게다가 C++에선 메모리 관리가 중요하니까, 파사드가 서브시스템 객체들의 생명주기를 관리하는 역할도 할 수 있다.


구조는 어떻게?

C++에서는 인터페이스를 추상 클래스로 정의하고, 서브시스템 클래스들이 이를 상속받아서 구체적인 기능을 구현한다. 파사드 클래스는 서브시스템들의 포인터를 멤버로 가지고, 클라이언트 요청을 받아서 적절히 위임한다.

  • Facade (파사드): 클라이언트가 사용하는 단순한 인터페이스. 서브시스템들의 포인터를 멤버로 가짐.
  • Subsystem Classes (서브시스템 클래스): 실제 기능을 수행하는 클래스들.
  • Client (클라이언트): 파사드를 통해 기능을 호출.

C++ 특성상 메모리 관리가 중요해서, 파사드의 소멸자에서 동적으로 할당된 서브시스템 객체들을 해제하는 것도 잊지 말아야함.


C++로 구현한 예제: 전투 시스템

게임의 전투 시스템을 예제. 플레이어, 적, 데미지 계산, 애니메이션, 사운드 이렇게 다섯 개 서브시스템이 있다고 치고, 파사드로 이걸 묶어본다면.

1. 서브시스템 클래스들

EnemySystem.h

#include <iostream>

class EnemySystem {
public:
    int health;
    EnemySystem(int h) : health(h) {}
    int attack() {
        int damage = rand() % 11 + 10; // 10~20
        std::cout << "[EnemySystem] 적이 " << damage << " 데미지를 입힙니다.\n";
        return damage;
    }
    void receiveDamage(int damage) {
        health -= damage;
        if (health < 0) health = 0;
        std::cout << "[EnemySystem] 적의 체력이 " << damage << " 감소, 현재 체력: " << health << "\n";
    }
};

PlayerSystem.h

#include <iostream>

class PlayerSystem {
public:
    int health;
    int mana;
    PlayerSystem(int h, int m) : health(h), mana(m) {}
    int attack() {
        int damage = rand() % 11 + 15; // 15~25
        std::cout << "[PlayerSystem] 플레이어가 " << damage << " 데미지를 입힙니다.\n";
        return damage;
    }
    void receiveDamage(int damage) {
        health -= damage;
        if (health < 0) health = 0;
        std::cout << "[PlayerSystem] 플레이어의 체력이 " << damage << " 감소, 현재 체력: " << health << "\n";
    }
    bool useMana(int amount) {
        if (mana < amount) {
            std::cout << "[PlayerSystem] 마나 부족!\n";
            return false;
        }
        mana -= amount;
        std::cout << "[PlayerSystem] 마나 " << amount << " 소모, 남은 마나: " << mana << "\n";
        return true;
    }
};

DamageCalculator.h

#include <iostream>

class DamageCalculator {
public:
    int calculateDamage(int baseDamage, int modifier) {
        int total = baseDamage + modifier;
        std::cout << "[DamageCalculator] 기본 " << baseDamage << "에 " << modifier << "를 더해 " << total << " 데미지.\n";
        return total;
    }
};

AnimationSystem.h

#include <iostream>
#include <chrono>
#include <thread>

class AnimationSystem {
public:
    void playAttackAnimation(const std::string& actor) {
        std::cout << "[AnimationSystem] " << actor << "의 공격 애니메이션 재생...\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        std::cout << "[AnimationSystem] " << actor << "의 공격 애니메이션 완료.\n";
    }
};

SoundSystem.h

#include <iostream>
#include <chrono>
#include <thread>

class SoundSystem {
public:
    void playSoundEffect(const std::string& effectName) {
        std::cout << "[SoundSystem] '" << effectName << "' 사운드 재생...\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        std::cout << "[SoundSystem] '" << effectName << "' 사운드 재생 완료.\n";
    }
};

2. 파사드 클래스: BattleFacade

BattleFacade.h

#include "EnemySystem.h"
#include "PlayerSystem.h"
#include "DamageCalculator.h"
#include "AnimationSystem.h"
#include "SoundSystem.h"

class BattleFacade {
private:
    EnemySystem* enemy;
    PlayerSystem* player;
    DamageCalculator* damageCalculator;
    AnimationSystem* animationSystem;
    SoundSystem* soundSystem;

public:
    BattleFacade(EnemySystem* e, PlayerSystem* p)
        : enemy(e), player(p),
          damageCalculator(new DamageCalculator()),
          animationSystem(new AnimationSystem()),
          soundSystem(new SoundSystem()) {}

    ~BattleFacade() {
        delete damageCalculator;
        delete animationSystem;
        delete soundSystem;
    }

    void executeBattleTurn() {
        std::cout << "\n--- 전투 턴 시작 ---\n";
        animationSystem->playAttackAnimation("플레이어");
        soundSystem->playSoundEffect("플레이어 공격 효과음");
        int playerDamage = damageCalculator->calculateDamage(player->attack(), 5);
        enemy->receiveDamage(playerDamage);
        if (enemy->health > 0) {
            animationSystem->playAttackAnimation("적");
            soundSystem->playSoundEffect("적 공격 효과음");
            int enemyDamage = damageCalculator->calculateDamage(enemy->attack(), -3);
            player->receiveDamage(enemyDamage);
        } else {
            std::cout << "[BattleFacade] 적이 이미 쓰러졌습니다.\n";
        }
        std::cout << "--- 전투 턴 종료 ---\n";
    }
};
  • 메모리 관리: BattleFacade가 동적으로 할당한 서브시스템 객체들(damageCalculator, animationSystem, soundSystem)을 소멸자에서 해제함. 하지만 enemyplayer는 외부에서 주입받은 포인터라 여기서 해제하지 않는다.

3. 클라이언트 코드

main.cpp

#include "BattleFacade.h"
#include <iostream>
#include <ctime>

int main() {
    std::srand(std::time(0)); // 랜덤 시드 설정
    EnemySystem* enemy = new EnemySystem(100);
    PlayerSystem* player = new PlayerSystem(120, 50);
    BattleFacade battleFacade(enemy, player);

    while (enemy->health > 0 && player->health > 0) {
        battleFacade.executeBattleTurn();
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    std::cout << "전투가 종료되었습니다.\n";
    delete enemy;
    delete player;
    return 0;
}
  • 클라이언트는 BattleFacadeexecuteBattleTurn()만 호출한다. 내부적으로 애니메이션, 사운드, 데미지 계산이 다 처리된다.

장단점?

장점

  • 인터페이스 단순화: 클라이언트가 복잡한 서브시스템을 몰라도 된다.
  • 결합도 감소: 서브시스템이 바뀌어도 클라이언트는 영향을 덜 받는다.
  • 유지보수 용이: 파사드에서 서브시스템을 관리하니까 수정이 쉽다.
  • C++ 특성 활용: 파사드가 메모리 관리를 맡아서 클라이언트 부담을 줄여준다.

단점

  • 유연성 제한: 세부 조정이 필요할 때 파사드가 방해가 될 수 있다.
  • 추가 계층: 파사드 클래스가 하나 더 생겨서 약간 복잡해질 수도 있다.

어디에 쓰면 좋을까?

  • 대규모 라이브러리: 복잡한 API를 단순하게 감쌀 때.
  • 레거시 시스템: 오래된 코드를 새 시스템에 통합할 때.
  • 게임 개발: 전투, UI, 네트워크 같은 여러 시스템을 하나로 묶을 때.

요약

파사드 패턴은 C++에서 복잡한 서브시스템을 단순한 인터페이스로 감싸줘서 코드의 복잡성을 줄이고 유지보수를 쉽게 해준다. 포인터와 메모리 관리를 신경 써야 하지만, 위 예제처럼 전투 시스템을 깔끔하게 통합할 수 있다. C++의 강력한 기능을 활용하면 더 큰 효과를 볼 수 있다.

0개의 댓글