소프트웨어 디자인 패턴의 핵심 원칙과 구현 예제

디자인 패턴의 7가지 기본 원칙

단일 책임 원칙(SRP): 클래스는 하나의 기능만을 담당해야 합니다.

  • 장점: 코드 유지보수성 향상 및 재사용 용이
  • 단점: 과도한 분할로 인한 클래스 수 증가

인터페이스 분리 원칙(ISP): 인터페이스는 작고 집중적으로 설계되어야 합니다.

  • 장점: 불필요한 메서드 제거로 유연성 향상
  • 단점: 인터페이스 개수 증가

개방-폐쇄 원칙(OCP): 확장에는 열려있고 수정에는 닫혀 있어야 합니다.

  • 장점: 시스템 확장성 향상 및 변경 리스크 감소
  • 단점: 지나친 추상화로 복잡도 증가

의존성 역전 원칙(DIP): 구체 클래스보다 추상화에 의존해야 합니다.

  • 장점: 모듈 간 결합도 감소 및 구현 교체 용이
  • 단점: 추가적인 추상 계층 정의 필요

리스코프 치환 원칙(LSP): 자식 클래스는 부모 클래스를 완전히 대체 가능해야 합니다.

  • 장점: 상속 체계의 논리적 일관성 보장
  • 단점: 자식 클래스의 유연성 제약

합성 재사용 원칙(CRP): 상속보다는 조합을 우선적으로 사용합니다.

  • 장점: 유연한 구조 및 높은 응집도
  • 단점: 객체 관계 관리의 복잡성

최소 지식 원칙(LoD): 객체 간 상호작용을 최소화해야 합니다.

  • 장점: 결합도 감소 및 모듈 독립성 향상
  • 단점: 중간 매개 메서드 증가

생성 패턴(Creational Patterns)

싱글톤 패턴

클래스의 인스턴스가 오직 하나만 존재하도록 보장하는 패턴입니다.

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <vector>
#include <functional>

class TaskManager {
public:
    static TaskManager* getInstance(size_t workerCount);
    template<typename Func, typename... Args>
    void submitTask(Func&& func, Args&&... args);
    ~TaskManager();

private:
    TaskManager(size_t count);
    TaskManager(const TaskManager&) = delete;
    TaskManager& operator=(const TaskManager&) = delete;

    static TaskManager* instance;
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> taskQueue;
    std::mutex queueMutex;
    std::condition_variable conditionVar;
    bool shutdownFlag;
};

TaskManager* TaskManager::instance = nullptr;

TaskManager::~TaskManager() {
    {
        std::lock_guard<std::mutex> lock(queueMutex);
        shutdownFlag = true;
    }
    conditionVar.notify_all();
    
    for(auto& worker : workers) {
        if(worker.joinable()) {
            worker.join();
        }
    }
    
    delete instance;
    instance = nullptr;
}

TaskManager::TaskManager(size_t count) : shutdownFlag(false) {
    for(size_t i = 0; i < count; ++i) {
        workers.emplace_back([this]() {
            while(true) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(queueMutex);
                    conditionVar.wait(lock, [this]() { 
                        return !taskQueue.empty() || shutdownFlag; 
                    });
                    
                    if(shutdownFlag && taskQueue.empty()) {
                        return;
                    }
                    
                    task = std::move(taskQueue.front());
                    taskQueue.pop();
                }
                task();
            }
        });
    }
}

template<typename Func, typename... Args>
void TaskManager::submitTask(Func&& func, Args&&... args) {
    auto task = std::bind(std::forward<Func>(func), std::forward<Args>(args)...);
    {
        std::lock_guard<std::mutex> lock(queueMutex);
        taskQueue.emplace(std::move(task));
    }
    conditionVar.notify_one();
}

TaskManager* TaskManager::getInstance(size_t workerCount) {
    static std::once_flag initFlag;
    std::call_once(initFlag, [workerCount]() {
        instance = new TaskManager(workerCount);
    });
    return instance;
}

팩토리 패턴

객체 생성 로직을 캡슐화하여 클라이언트와 구체 클래스 간의 결합도를 낮춥니다.

#include <iostream>
#include <memory>
#include <string>

class Device {
public:
    virtual void powerOn() = 0;
    virtual void powerOff() = 0;
    virtual ~Device() = default;
};

class MobileDevice : public Device {
public:
    void powerOn() override {
        std::cout << "휴대폰 전원 켜짐\n";
    }
    
    void powerOff() override {
        std::cout << "휴대폰 전원 꺼짐\n";
    }
};

class DesktopDevice : public Device {
public:
    void powerOn() override {
        std::cout << "데스크톱 전원 켜짐\n";
    }
    
    void powerOff() override {
        std::cout << "데스크톱 전원 꺼짐\n";
    }
};

class DeviceFactory {
public:
    virtual std::unique_ptr<Device> createDevice() = 0;
    virtual ~DeviceFactory() = default;
};

class MobileFactory : public DeviceFactory {
public:
    std::unique_ptr<Device> createDevice() override {
        return std::make_unique<MobileDevice>();
    }
};

class DesktopFactory : public DeviceFactory {
public:
    std::unique_ptr<Device> createDevice() override {
        return std::make_unique<DesktopDevice>();
    }
};

추상 팩토리 패턴

관련된 객체들의 집합을 생성하기 위한 인터페이스를 제공합니다.

class DatabaseConnection {
public:
    virtual void connect() = 0;
    virtual ~DatabaseConnection() = default;
};

class DatabaseCommand {
public:
    virtual void execute() = 0;
    virtual ~DatabaseCommand() = default;
};

class MySQLConnection : public DatabaseConnection {
public:
    void connect() override {
        std::cout << "MySQL 연결\n";
    }
};

class MySQLCommand : public DatabaseCommand {
public:
    void execute() override {
        std::cout << "MySQL 명령 실행\n";
    }
};

class SQLServerConnection : public DatabaseConnection {
public:
    void connect() override {
        std::cout << "SQL Server 연결\n";
    }
};

class SQLServerCommand : public DatabaseCommand {
public:
    void execute() override {
        std::cout << "SQL Server 명령 실행\n";
    }
};

class DatabaseFactory {
public:
    virtual std::unique_ptr<DatabaseConnection> createConnection() = 0;
    virtual std::unique_ptr<DatabaseCommand> createCommand() = 0;
    virtual ~DatabaseFactory() = default;
};

class MySQLFactory : public DatabaseFactory {
public:
    std::unique_ptr<DatabaseConnection> createConnection() override {
        return std::make_unique<MySQLConnection>();
    }
    
    std::unique_ptr<DatabaseCommand> createCommand() override {
        return std::make_unique<MySQLCommand>();
    }
};

class SQLServerFactory : public DatabaseFactory {
public:
    std::unique_ptr<DatabaseConnection> createConnection() override {
        return std::make_unique<SQLServerConnection>();
    }
    
    std::unique_ptr<DatabaseCommand> createCommand() override {
        return std::make_unique<SQLServerCommand>();
    }
};

구조 패턴(Structural Patterns)

어댑터 패턴

기존 인터페이스를 새로운 인터페이스로 변환합니다.

#include <iostream>

class ChargerInterface {
public:
    virtual void chargeDevice(int voltage) = 0;
    virtual ~ChargerInterface() = default;
};

class PhoneCharger {
public:
    void charge(int inputA, int inputB) {
        std::cout << "휴대폰 충전 중\n";
    }
};

template<typename T>
class ChargerAdapter : public ChargerInterface {
public:
    explicit ChargerAdapter(T& device) : targetDevice(device) {}
    
    void chargeDevice(int voltage) override {
        targetDevice.charge(voltage, voltage + 1);
    }

private:
    T& targetDevice;
};

프록시 패턴

실제 객체에 대한 접근을 제어하는 대리 객체를 제공합니다.

class PropertyInterface {
public:
    virtual void lease() = 0;
    virtual ~PropertyInterface() = default;
};

class Landlord : public PropertyInterface {
public:
    void lease() override {
        std::cout << "부동산 임대\n";
    }
};

class RealEstateAgent : public PropertyInterface {
public:
    explicit RealEstateAgent(Landlord& owner) : propertyOwner(owner) {}
    
    void lease() override {
        advertise();
        propertyOwner.lease();
    }

private:
    void advertise() {
        std::cout << "광고 시작\n";
    }
    
    Landlord& propertyOwner;
};

퍼사드 패턴

복잡한 서브시스템을 단순한 인터페이스로 제공합니다.

class StockInventory {
public:
    bool isAvailable() const { return quantity > 0; }
private:
    int quantity = 10;
};

class OrderProcessor {
public:
    std::string generateOrderId() { return "ORD-12345"; }
};

class PaymentHandler {
public:
    bool processPayment(const std::string& orderId) { return true; }
};

class DeliveryService {
public:
    void dispatchPackage() { std::cout << "배송 시작\n"; }
};

class OrderFacade {
public:
    void processOrder() {
        if(inventory.isAvailable()) {
            std::string orderId = processor.generateOrderId();
            if(payment.processPayment(orderId)) {
                delivery.dispatchPackage();
            }
        }
    }

private:
    StockInventory inventory;
    OrderProcessor processor;
    PaymentHandler payment;
    DeliveryService delivery;
};

데코레이터 패턴

객체에 동적으로 새로운 기능을 추가합니다.

class GameCharacter {
public:
    virtual int getAttackPower() = 0;
    virtual ~GameCharacter() = default;
protected:
    int basePower = 20;
};

class Warrior : public GameCharacter {
public:
    int getAttackPower() override {
        return basePower + 5;
    }
};

class Mage : public GameCharacter {
public:
    int getAttackPower() override {
        return basePower;
    }
};

class EquipmentDecorator : public GameCharacter {
public:
    explicit EquipmentDecorator(GameCharacter* character) : decoratedCharacter(character) {}
protected:
    GameCharacter* decoratedCharacter;
};

class WeaponDecorator : public EquipmentDecorator {
public:
    explicit WeaponDecorator(GameCharacter* character) : EquipmentDecorator(character) {}
    
    int getAttackPower() override {
        return decoratedCharacter->getAttackPower() + 10;
    }
};

class ArmorDecorator : public EquipmentDecorator {
public:
    explicit ArmorDecorator(GameCharacter* character) : EquipmentDecorator(character) {}
    
    int getAttackPower() override {
        return decoratedCharacter->getAttackPower() + 5;
    }
};

브리지 패턴

추상화와 구현을 분리하여 각각 독립적으로 확장할 수 있게 합니다.

class PresentationMethod {
public:
    virtual void render() = 0;
    virtual ~PresentationMethod() = default;
};

class VideoRenderer : public PresentationMethod {
public:
    void render() override {
        std::cout << "비디오 형식으로 렌더링\n";
    }
};

class DocumentRenderer : public PresentationMethod {
public:
    void render() override {
        std::cout << "문서 형식으로 렌더링\n";
    }
};

class Course {
public:
    explicit Course(std::unique_ptr<PresentationMethod> method) : renderingMethod(std::move(method)) {}
    virtual void present() = 0;
protected:
    std::unique_ptr<PresentationMethod> renderingMethod;
};

class ProgrammingCourse : public Course {
public:
    explicit ProgrammingCourse(std::unique_ptr<PresentationMethod> method) : Course(std::move(method)) {}
    
    void present() override {
        std::cout << "프로그래밍 강의\n";
        renderingMethod->render();
    }
};

class DesignCourse : public Course {
public:
    explicit DesignCourse(std::unique_ptr<PresentationMethod> method) : Course(std::move(method)) {}
    
    void present() override {
        std::cout << "디자인 강의\n";
        renderingMethod->render();
    }
};

행동 패턴(Behavioral Patterns)

템플릿 메서드 패턴

알고리즘의 구조를 정의하고 하위 클래스에서 특정 단계를 구현하도록 합니다.

class BeveragePreparation {
public:
    void prepare() {
        boilWater();
        addIngredients();
        steep();
        serve();
    }

protected:
    void boilWater() {
        std::cout << "물 끓이기\n";
    }
    
    virtual void addIngredients() = 0;
    
    void steep() {
        std::cout << "우려내기\n";
    }
    
    void serve() {
        std::cout << "컵에 따르기\n";
    }
};

class TeaPreparation : public BeveragePreparation {
protected:
    void addIngredients() override {
        std::cout << "차 잎 추가\n";
    }
};

class CoffeePreparation : public BeveragePreparation {
protected:
    void addIngredients() override {
        std::cout << "커피 가루 추가\n";
    }
};

전략 패턴

알고리즘군을 정의하고 캡슐화하여 상호교환이 가능하게 합니다.

class PricingStrategy {
public:
    virtual double calculateFinalPrice(double original) = 0;
    virtual ~PricingStrategy() = default;
};

class BulkDiscount : public PricingStrategy {
private:
    double threshold, discountAmount;
public:
    BulkDiscount(double minAmount, double reduction) 
        : threshold(minAmount), discountAmount(reduction) {}
        
    double calculateFinalPrice(double original) override {
        return original >= threshold ? original - discountAmount : original;
    }
};

class PercentageDiscount : public PricingStrategy {
private:
    double rate;
public:
    explicit PercentageDiscount(double discountRate) : rate(discountRate) {}
    
    double calculateFinalPrice(double original) override {
        return original * rate;
    }
};

class VoucherDiscount : public PricingStrategy {
private:
    double voucherValue;
public:
    explicit VoucherDiscount(double value) : voucherValue(value) {}
    
    double calculateFinalPrice(double original) override {
        return std::max(0.0, original - voucherValue);
    }
};

class ShoppingBasket {
public:
    explicit ShoppingBasket(std::unique_ptr<PricingStrategy> strategy) 
        : currentStrategy(std::move(strategy)) {}
        
    void changeStrategy(std::unique_ptr<PricingStrategy> newStrategy) {
        currentStrategy = std::move(newStrategy);
    }
    
    void checkout(double price) {
        double final = currentStrategy->calculateFinalPrice(price);
        std::cout << "최종 금액: " << final << "원\n";
    }

private:
    std::unique_ptr<PricingStrategy> currentStrategy;
};

옵저버 패턴

객체 상태 변화를 다른 객체들에게 알리는 메커니즘을 제공합니다.

#include <vector>
#include <algorithm>

class MarketObserver;

class StockSubject {
protected:
    std::vector<MarketObserver*> observers;
    double currentPrice;

public:
    void addObserver(MarketObserver* observer) {
        observers.push_back(observer);
    }
    
    void removeObserver(MarketObserver* observer) {
        auto it = std::find(observers.begin(), observers.end(), observer);
        if(it != observers.end()) {
            observers.erase(it);
        }
    }
    
    void notifyObservers() {
        for(auto* observer : observers) {
            observer->update(currentPrice);
        }
    }
    
    void setPrice(double price) {
        currentPrice = price;
        notifyObservers();
    }
};

class MarketObserver {
public:
    virtual void update(double price) = 0;
    virtual ~MarketObserver() = default;
};

class IndividualInvestor : public MarketObserver {
private:
    std::string investorName;
public:
    explicit IndividualInvestor(const std::string& name) : investorName(name) {}
    
    void update(double price) override {
        std::cout << investorName << ": 주가 " << price << "원 확인\n";
    }
};

class InstitutionalInvestor : public MarketObserver {
private:
    std::string fundName;
public:
    explicit InstitutionalInvestor(const std::string& name) : fundName(name) {}
    
    void update(double price) override {
        std::cout << fundName << ": 주가 " << price << "원, 분석 시작...\n";
    }
};

태그: C++ design-patterns singleton factory adapter

6월 4일 21:59에 게시됨