본문 바로가기
-----ETC-----/C++ 마스터 시리즈

[C++ 마스터] Day 29: C++에서의 디자인 패턴

by cogito21_cpp 2024. 8. 1.
반응형

디자인 패턴은 소프트웨어 설계에서 자주 발생하는 문제를 해결하기 위한 재사용 가능한 솔루션입니다. 디자인 패턴은 크게 세 가지 유형으로 분류됩니다: 생성 패턴(Creational Patterns), 구조 패턴(Structural Patterns), 행위 패턴(Behavioral Patterns).

 

1. 생성 패턴 (Creational Patterns)

생성 패턴은 객체 생성 메커니즘을 다루며, 객체 생성의 유연성과 재사용성을 향상시킵니다.

 

1.1 싱글톤 패턴 (Singleton Pattern)

싱글톤 패턴은 클래스의 인스턴스를 하나만 만들고, 그 인스턴스에 대한 전역적인 접근을 제공합니다.

#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;
    Singleton() {}  // 생성자를 private으로

public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    void showMessage() {
        std::cout << "Hello from Singleton!" << std::endl;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    Singleton* s = Singleton::getInstance();
    s->showMessage();
    return 0;
}

 

2. 구조 패턴 (Structural Patterns)

구조 패턴은 클래스와 객체를 구성하는 방법을 다룹니다.

 

2.1 어댑터 패턴 (Adapter Pattern)

어댑터 패턴은 인터페이스가 호환되지 않는 클래스를 함께 작동하도록 합니다.

#include <iostream>

// 타겟 인터페이스
class Target {
public:
    virtual void request() {
        std::cout << "Target request" << std::endl;
    }
};

// 적응해야 할 인터페이스
class Adaptee {
public:
    void specificRequest() {
        std::cout << "Adaptee specific request" << std::endl;
    }
};

// 어댑터 클래스
class Adapter : public Target {
private:
    Adaptee* adaptee;

public:
    Adapter(Adaptee* a) : adaptee(a) {}

    void request() override {
        adaptee->specificRequest();
    }
};

int main() {
    Adaptee* adaptee = new Adaptee();
    Target* adapter = new Adapter(adaptee);
    adapter->request();
    delete adaptee;
    delete adapter;
    return 0;
}

 

3. 행위 패턴 (Behavioral Patterns)

행위 패턴은 객체 간의 상호작용과 책임 분배를 다룹니다.

 

3.1 전략 패턴 (Strategy Pattern)

전략 패턴은 알고리즘 군을 정의하고, 각각을 캡슐화하며, 이들을 상호 교환 가능하게 만듭니다.

#include <iostream>

// 전략 인터페이스
class Strategy {
public:
    virtual void execute() = 0;
};

// 구체적인 전략
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "ConcreteStrategyA execution" << std::endl;
    }
};

class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "ConcreteStrategyB execution" << std::endl;
    }
};

// 컨텍스트 클래스
class Context {
private:
    Strategy* strategy;

public:
    Context(Strategy* s) : strategy(s) {}

    void setStrategy(Strategy* s) {
        strategy = s;
    }

    void executeStrategy() {
        strategy->execute();
    }
};

int main() {
    Strategy* strategyA = new ConcreteStrategyA();
    Strategy* strategyB = new ConcreteStrategyB();

    Context context(strategyA);
    context.executeStrategy();

    context.setStrategy(strategyB);
    context.executeStrategy();

    delete strategyA;
    delete strategyB;
    return 0;
}

 

예제 문제

문제 1: 싱글톤 패턴을 사용하여 로깅 클래스 작성

싱글톤 패턴을 사용하여 로깅 클래스를 작성하고, 여러 번의 로그 메시지를 기록하는 프로그램을 작성하세요.

 

해설:

#include <iostream>
#include <mutex>

class Logger {
private:
    static Logger* instance;
    static std::mutex mtx;
    Logger() {}  // 생성자를 private으로

public:
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    static Logger* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Logger();
            }
        }
        return instance;
    }

    void logMessage(const std::string& message) {
        std::cout << "Log: " << message << std::endl;
    }
};

Logger* Logger::instance = nullptr;
std::mutex Logger::mtx;

int main() {
    Logger* logger = Logger::getInstance();
    logger->logMessage("This is the first log message.");
    logger->logMessage("This is the second log message.");
    return 0;
}

 

이 프로그램은 싱글톤 패턴을 사용하여 로깅 클래스를 작성하고, 여러 번의 로그 메시지를 기록합니다.

 

문제 2: 어댑터 패턴을 사용하여 다양한 미디어 플레이어 구현

어댑터 패턴을 사용하여 다양한 미디어 플레이어를 지원하는 프로그램을 작성하세요. 기본 플레이어는 MP3 파일을 재생하고, 어댑터를 사용하여 MP4와 VLC 파일을 재생합니다.

 

해설:

#include <iostream>

// 타겟 인터페이스
class MediaPlayer {
public:
    virtual void play(const std::string& audioType, const std::string& fileName) = 0;
};

// 적응해야 할 인터페이스
class AdvancedMediaPlayer {
public:
    virtual void playMP4(const std::string& fileName) = 0;
    virtual void playVLC(const std::string& fileName) = 0;
};

// 구체적인 어댑티 클래스
class MP4Player : public AdvancedMediaPlayer {
public:
    void playMP4(const std::string& fileName) override {
        std::cout << "Playing MP4 file. Name: " << fileName << std::endl;
    }
    void playVLC(const std::string& fileName) override {
        // 아무 작업도 하지 않음
    }
};

class VLCPlayer : public AdvancedMediaPlayer {
public:
    void playMP4(const std::string& fileName) override {
        // 아무 작업도 하지 않음
    }
    void playVLC(const std::string& fileName) override {
        std::cout << "Playing VLC file. Name: " << fileName << std::endl;
    }
};

// 어댑터 클래스
class MediaAdapter : public MediaPlayer {
private:
    AdvancedMediaPlayer* advancedMusicPlayer;

public:
    MediaAdapter(const std::string& audioType) {
        if (audioType == "mp4") {
            advancedMusicPlayer = new MP4Player();
        } else if (audioType == "vlc") {
            advancedMusicPlayer = new VLCPlayer();
        }
    }

    void play(const std::string& audioType, const std::string& fileName) override {
        if (audioType == "mp4") {
            advancedMusicPlayer->playMP4(fileName);
        } else if (audioType == "vlc") {
            advancedMusicPlayer->playVLC(fileName);
        }
    }
};

// 구체적인 플레이어 클래스
class AudioPlayer : public MediaPlayer {
private:
    MediaAdapter* mediaAdapter;

public:
    void play(const std::string& audioType, const std::string& fileName) override {
        if (audioType == "mp3") {
            std::cout << "Playing MP3 file. Name: " << fileName << std::endl;
        } else if (audioType == "mp4" || audioType == "vlc") {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter->play(audioType, fileName);
        } else {
            std::cout << "Invalid media. " << audioType << " format not supported" << std::endl;
        }
    }
};

int main() {
    AudioPlayer* audioPlayer = new AudioPlayer();

    audioPlayer->play("mp3", "beyond_the_horizon.mp3");
    audioPlayer->play("mp4", "alone.mp4");
    audioPlayer->play("vlc", "far_far_away.vlc");
    audioPlayer->play("avi", "mind_me.avi");

    delete audioPlayer;
    return 0;
}

 

이 프로그램은 어댑터 패턴을 사용하여 다양한 미디어 플레이어를 지원합니다.

 

문제 3: 전략 패턴을 사용하여 다양한 정렬 알고리즘 구현

전략 패턴을 사용하여 다양한 정렬 알고리즘을 지원하는 프로그램을 작성하세요. 기본적으로 버블 정렬을 사용하고, 선택 정렬과 삽입 정렬을 옵션으로 제공하세요.

 

해설:

#include <iostream>
#include <vector>

// 전략 인터페이스
class SortStrategy {
public:
    virtual void sort(std::vector<int>& data) = 0;
};

// 구체적인 전략: 버블 정렬
class BubbleSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        int n = data.size();
        for (int i = 0; i < n - 1; ++i) {
            for (int j = 0; j < n - i - 1; ++j) {
                if (data[j] > data[j + 1]) {
                    std::swap(data[j], data[j + 1]);
                }
            }
        }
    }
};

// 구체적인 전략: 선택 정렬
class SelectionSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        int n = data.size();
        for (int i = 0; i < n - 1; ++i) {
            int min_idx = i;
            for (int j = i + 1; j < n; ++j) {
                if (data[j] < data[min_idx]) {
                    min_idx = j;
                }
            }
            std::swap(data[i], data[min_idx]);
        }
    }
};

// 구체적인 전략: 삽입 정렬
class InsertionSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        int n = data.size();
        for (int i = 1; i < n; ++i) {
            int key = data[i];
            int j = i - 1;
            while (j >= 0 && data[j] > key) {
                data[j + 1] = data[j];
                --j;
            }
            data[j + 1] = key;
        }
    }
};

// 컨텍스트 클래스
class SortContext {
private:
    SortStrategy* strategy;

public:
    SortContext(SortStrategy* s) : strategy(s) {}

    void setStrategy(SortStrategy* s) {
        strategy = s;
    }

    void sort(std::vector<int>& data) {
        strategy->sort(data);
    }
};

int main() {
    std::vector<int> data = {5, 2, 9, 1, 5, 6};

    BubbleSort bubbleSort;
    SelectionSort selectionSort;
    InsertionSort insertionSort;

    SortContext context(&bubbleSort);
    context.sort(data);

    std::cout << "Bubble Sort: ";
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    data = {5, 2, 9, 1, 5, 6};
    context.setStrategy(&selectionSort);
    context.sort(data);

    std::cout << "Selection Sort: ";
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    data = {5, 2, 9, 1, 5, 6};
    context.setStrategy(&insertionSort);
    context.sort(data);

    std::cout << "Insertion Sort: ";
    for (int num : data) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

 

이 프로그램은 전략 패턴을 사용하여 다양한 정렬 알고리즘을 지원합니다.

 

다음 단계

29일차의 목표는 C++에서의 디자인 패턴에 대해 학습하는 것이었습니다. 다음 날은 C++ 개발자로서의 커리어 개발 및 다음 단계에 대해 다룰 것입니다.

 

내일은 "C++ 개발자로서의 커리어 개발 및 다음 단계"에 대해 다룰 예정입니다. 질문이나 피드백이 있으면 댓글로 남겨 주세요!

반응형