코딩 학습/C와 C++

C++ 기초 - 디자인 패턴(싱글톤, 데코레이터, 옵저버)

이개 2026. 3. 17. 19:12

생성 패턴

  • 객체 생성하는데 제한을 두고 싶을때 사용하는 패턴.
  • 예) 싱글톤

 

구조 패턴

  • 객체들의 구조를 구성하는 패턴
  • 예) 데코레이터

 

행동 패턴

  • 객체의 상태가 변화할 때 이 상태를 어떻게 전달할지에 대한 패턴.
  • 예) 옵저버

 

싱글톤

비행기 게임을 만든다고 하자. 게임 내 비행기는 오직 하나만 존재한다. 비행기의 초기 위치는 (0, 0)  좌표이다.

  1. 비행기는 move(int deltaX, int deltaY) 메서드를 통해 이동하고, 위치를 업데이트 한다.
  2. 현재 비행기 좌표를 확인할 수 있는 getPosition()을 제공한다.

비행기가 하나만 존재하도록 구현한다.

  • 생성 및 대입은 private으로, getInstance()를 통해서만 인스턴스를 받는다.
  •  
#include <iostream>
using namespace std;

class Airplane {
private:
    static Airplane* instance; // 유일한 비행기 객체를 가리킬 정적 포인터
    int positionX;             // 비행기의 X 위치
    int positionY;             // 비행기의 Y 위치

    // private 생성자: 외부에서 객체 생성 금지
    Airplane() : positionX(0), positionY(0) {
        cout << "Airplane Created at (" << positionX << ", " << positionY << ")" << endl;
    }

public:
    // 복사 생성자와 대입 연산자를 삭제하여 복사 방지
    Airplane(const Airplane&) = delete;
    Airplane& operator=(const Airplane&) = delete;

    // 정적 메서드: 유일한 비행기 인스턴스를 반환
    static Airplane* getInstance() {
        if (instance == nullptr) {
            instance = new Airplane();
        }
        return instance;
    }

    // 비행기 위치 이동
    void move(int deltaX, int deltaY) {
        positionX += deltaX;
        positionY += deltaY;
        cout << "Airplane moved to (" << positionX << ", " << positionY << ")" << endl;
    }

    // 현재 위치 출력
    void getPosition() const {
        cout << "Airplane Position: (" << positionX << ", " << positionY << ")" << endl;
    }
};

// 정적 멤버 초기화
Airplane* Airplane::instance = nullptr;

// 메인 함수 (사용 예시)
int main() {
    // 유일한 비행기 인스턴스를 가져옴
    Airplane* airplane = Airplane::getInstance();
    airplane->move(10, 20);  // 비행기 이동
    airplane->getPosition();

    // 또 다른 요청도 같은 인스턴스를 반환
    Airplane* sameAirplane = Airplane::getInstance();
    sameAirplane->move(-5, 10); // 비행기 이동
    sameAirplane->getPosition();

    return 0;
}

 

    Airplane(const Airplane&) = delete;
    Airplane& operator=(const Airplane&) = delete;

 

delete는 그 함수를 사용하지 못하게 막는 C++문법이다.

 

 

데코레이터 패턴

#include <iostream>
#include <string>

using namespace std;

// **추상 컴포넌트 (Component): Pizza**
// - 피자 객체의 기본 구조를 정의하는 인터페이스입니다.
// - 모든 피자는 이름(`getName`)과 가격(`getPrice`)을 가져야 합니다.
class Pizza {
public:
    virtual ~Pizza() {}
    virtual string getName() const = 0;  // 피자의 이름 반환
    virtual double getPrice() const = 0; // 피자의 가격 반환
};

// **구체 컴포넌트 (Concrete Component): BasicPizza**
// - 기본 피자 클래스입니다.
// - 피자의 기본 베이스(이름과 가격)를 구현합니다.
class BasicPizza : public Pizza {
public:
    string getName() const {
        return "Basic Pizza"; // 기본 피자의 이름
    }
    double getPrice() const {
        return 5.0; // 기본 피자의 가격
    }
};

// **데코레이터 추상 클래스 (Decorator): PizzaDecorator**
// - 기존 피자의 기능을 확장하기 위한 데코레이터의 기본 구조를 정의합니다.
// - 내부적으로 `Pizza` 객체를 감싸며, 이름과 가격에 추가적인 기능을 제공합니다.
class PizzaDecorator : public Pizza {
protected:
    Pizza* pizza; // 기존의 피자 객체를 참조합니다.
public:
    // 데코레이터는 피자 객체를 받아서 감쌉니다.
    PizzaDecorator(Pizza* p) : pizza(p) {}
    
    // 소멸자에서 내부 피자 객체를 삭제합니다.
    virtual ~PizzaDecorator() {
        delete pizza;
    }
};

// **구체 데코레이터 (Concrete Decorators): Cheese, Pepperoni, Olive**
// - 각각의 토핑 데코레이터는 `PizzaDecorator`를 상속받아 이름과 가격을 확장합니다.

// 치즈 토핑 데코레이터
class CheeseDecorator : public PizzaDecorator {
public:
    CheeseDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Cheese"를 추가
        return pizza->getName() + " + Cheese";
    }
    double getPrice() const {
        // 기존 피자의 가격에 치즈 추가 비용 1.5를 더함
        return pizza->getPrice() + 1.5;
    }
};

// 페퍼로니 토핑 데코레이터
class PepperoniDecorator : public PizzaDecorator {
public:
    PepperoniDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Pepperoni"를 추가
        return pizza->getName() + " + Pepperoni";
    }
    double getPrice() const {
        // 기존 피자의 가격에 페퍼로니 추가 비용 2.0을 더함
        return pizza->getPrice() + 2.0;
    }
};

// 올리브 토핑 데코레이터
class OliveDecorator : public PizzaDecorator {
public:
    OliveDecorator(Pizza* p) : PizzaDecorator(p) {}
    string getName() const {
        // 기존 피자의 이름에 " + Olive"를 추가
        return pizza->getName() + " + Olive";
    }
    double getPrice() const {
        // 기존 피자의 가격에 올리브 추가 비용 0.7을 더함
        return pizza->getPrice() + 0.7;
    }
};

// **클라이언트 코드**
// - 피자와 데코레이터를 조합하여 최종 피자를 생성하고, 정보를 출력합니다.
int main() {
    // 1. 기본 피자를 생성합니다.
    Pizza* pizza = new BasicPizza();

    // 2. 치즈 토핑을 추가합니다.
    pizza = new CheeseDecorator(pizza);

    // 3. 페퍼로니 토핑을 추가합니다.
    pizza = new PepperoniDecorator(pizza);

    // 4. 올리브 토핑을 추가합니다.
    pizza = new OliveDecorator(pizza);

    // 5. 최종 피자 정보 출력
    cout << "Pizza: " << pizza->getName() << endl; // 피자의 이름 출력
    cout << "Price: $" << pizza->getPrice() << endl; // 피자의 가격 출력

    // 6. 메모리 해제
    delete pizza;

    return 0;
}

 

위와 같이 인터페이스<-베이직 구현 한 후에 인터페이스를 멤버로 갖는 데코레이터 클래스를 만들고, 데코레이터 클래스를 상속받아서 추가 구현을 하는 파생 클래스들을 만든다.

 

옵저버 패턴

Data의 변경을 확인할 수 있도록 옵저버들을 등록해놓고, Data가 변경될 때마다 변경 알림을 호출한다.

#include <iostream>
#include <vector>
#include <string>
using namespace std;

// Observer 인터페이스
// - Observer 패턴에서 상태 변화를 알림받는 객체들의 공통 인터페이스
// - Observer들은 이 인터페이스를 구현하여 `update` 메서드를 통해 데이터를 전달받음
class Observer {
public:
    virtual ~Observer() = default;               // 가상 소멸자
    virtual void update(int data) = 0;           // 데이터 업데이트 메서드 (순수 가상 함수)
};

// Subject 클래스 (엑셀 시트 역할)
// - 데이터의 상태 변화를 관리하며, 모든 등록된 Observer들에게 변경 사항을 알림
class ExcelSheet {
private:
    vector<Observer*> observers;                 // Observer들을 저장하는 리스트
    int data;                                    // 현재 데이터 상태

public:
    ExcelSheet() : data(0) {}                    // 생성자: 초기 데이터 값은 0

    // Observer 등록 메서드
    // - 새로운 Observer를 등록하여 변경 사항 알림을 받을 수 있도록 추가
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    // 데이터 변경 알림 메서드
    // - 등록된 모든 Observer들의 `update` 메서드를 호출하여 데이터 변경 사항을 알림
    void notify() {
        for (Observer* observer : observers) {
            observer->update(data);              // 각 Observer에게 데이터를 전달
        }
    }

    // 데이터 설정 메서드
    // - 데이터를 변경하고 변경 사항을 모든 Observer에게 알림
    void setData(int newData) {
        data = newData;                          // 새로운 데이터로 갱신
        cout << "ExcelSheet: Data updated to " << data << endl;
        notify();                                // Observer들에게 알림
    }
};

// 구체적인 Observer 클래스: BarChart (막대 차트)
// - 데이터를 막대 그래프로 표현
class BarChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "BarChart: Displaying data as vertical bars: ";
        for (int i = 0; i < data; ++i) {
            cout << "|";                         // 데이터 값만큼 막대 출력
        }
        cout << " (" << data << ")" << endl;
    }
};

// 구체적인 Observer 클래스: LineChart (라인 차트)
// - 데이터를 선형 그래프로 표현
class LineChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "LineChart: Plotting data as a line: ";
        for (int i = 0; i < data; ++i) {
            cout << "-";                         // 데이터 값만큼 선 출력
        }
        cout << " (" << data << ")" << endl;
    }
};

// 구체적인 Observer 클래스: PieChart (파이 차트)
// - 데이터를 파이 그래프로 표현
class PieChart : public Observer {
public:
    void update(int data) {                      // 데이터 업데이트 시 호출됨
        cout << "PieChart: Displaying data as a pie chart slice: ";
        cout << "Pie [" << data << "%]" << endl; // 데이터 값 출력 (가정: % 비율로 표현)
    }
};

// 메인 함수
int main() {
    // Subject 생성
    ExcelSheet excelSheet;                       // 데이터를 관리하는 엑셀 시트 객체 생성

    // Observer 객체 생성 (각 차트 객체)
    BarChart* barChart = new BarChart();         // 막대 차트 생성
    LineChart* lineChart = new LineChart();      // 라인 차트 생성
    PieChart* pieChart = new PieChart();         // 파이 차트 생성

    // Observer 등록
    // - 각 차트(Observer)를 엑셀 시트(Subject)에 등록
    excelSheet.attach(barChart);
    excelSheet.attach(lineChart);
    excelSheet.attach(pieChart);

    // 데이터 변경 테스트
    // - 데이터를 변경하면 등록된 모든 Observer들이 알림을 받고 화면에 갱신
    excelSheet.setData(5);                       // 데이터 변경: 5
    excelSheet.setData(10);                      // 데이터 변경: 10

    // 메모리 해제
    // - 동적 할당된 Observer(차트) 객체 삭제
    delete barChart;
    delete lineChart;
    delete pieChart;

    return 0;
}

학습 노트

template<>가 뭔지

 
// 일반 템플릿
template <typename T>
void func(T a) { cout << "일반" << endl; }

// 특수화: 특정 타입에 대해 다른 동작 정의
template <>              // ← "특수화할게!" 선언
void func<int>(int a) { cout << "int 특수화" << endl; }

func(3.14);  // "일반"
func(42);    // "int 특수화"

 

 

템플릿인자T 추론인자 타입

func(T a) int x T = int int
func(T& a) int x T = int int&
func(T& a) int& x T = int int&

 

std::tuple

서로 다른 타입 여러 개를 하나로 묶는 컨테이너.

 
// pair는 딱 2개만
pair<int, string> p(1, "hello");

// tuple은 개수 제한 없음
tuple<int, string, double, bool> t(1, "hello", 3.14, true);
#include <tuple>

tuple<int, string, double> t(42, "hello", 3.14);

// 값 꺼내기 → get<인덱스>()
cout << get<0>(t) << endl;  // 42
cout << get<1>(t) << endl;  // "hello"
cout << get<2>(t) << endl;  // 3.14

// make_tuple로 간편 생성
auto t2 = make_tuple(42, "hello", 3.14);

사용하는 경우

// 함수에서 여러 값을 반환할 때
tuple<int, string, bool> getInfo() {
    return make_tuple(42, "hello", true);
}

auto [id, name, active] = getInfo();  // C++17 구조적 바인딩
cout << id << " " << name << endl;   // 42 hello

 

 

구조적 바인딩 (Structured Binding)

tuple의 요소를 한 번에 여러 변수로 분해하는 C++17 문법이야.

 
auto t = make_tuple(42, "hello", 3.14);

// 기존 방식 → 하나씩 꺼내야 함
int a = get<0>(t);
string b = get<1>(t);
double c = get<2>(t);

// 구조적 바인딩 → 한 번에 분해!
auto [a, b, c] = t;
// a = 42, b = "hello", c = 3.14

tuple뿐만 아니라 pair, 구조체에도 써!

 
 
// pair 분해
pair<int, string> p = {1, "hello"};
auto [num, str] = p;
// num = 1, str = "hello"

// 구조체 분해
struct Point { int x, y; };
Point p = {3, 5};
auto [x, y] = p;
// x = 3, y = 5

 

 

T&(non-const 참조)로 받을 수 없는 것

 
template <typename T>
void func(T& a) { }

T&는 "이 값을 수정할 수도 있어" 라는 의미야. 그래서 수정 불가능한 것들은 받을 수 없어!

b번 - const int x = 5가 안 되는 이유

 
const int x = 5;
func(x);  // ❌

// func(T& a)는 수정 가능한 참조를 요구
// 근데 x는 const → 수정 불가
// "수정 가능하다고 약속"하면서 const를 받는 건 위험 → 에러!

 

오른쪽에서 왼쪽으로 읽는 법을 쓰면 쉬워!

 
 
const int* p
```
오른쪽부터 읽으면:
```
p         → "p는"
*         → "포인터인데"
const int → "const int를 가리킴"
→ p는 const int를 가리키는 포인터
→ 가리키는 값 변경 불가, 포인터 재할당 가능
 
 
int* const q
```
오른쪽부터 읽으면:
```
q         → "q는"
const     → "const"
*         → "포인터인데"
int       → "int를 가리킴"
→ q는 int를 가리키는 const 포인터
→ 포인터 재할당 불가, 가리키는 값 변경 가능

코드로 확인

 
int x = 5, y = 10;

const int* p = &x;  // 값이 const
*p = 20;   // ❌ 값 변경 불가
p = &y;    // ✅ 포인터 재할당 가능

int* const q = &x;  // 포인터가 const
*q = 20;   // ✅ 값 변경 가능
q = &y;    // ❌ 포인터 재할당 불가
```

### 외우는 법
```
const int* p  → const가 * 왼쪽 → 값이 const
int* const q  → const가 * 오른쪽 → 포인터가 const
const int* pint* const q
*p = 20 (값 변경)
p = &y (재할당)

'코딩 학습 > C와 C++' 카테고리의 다른 글

C++ 기초 - 스택, 큐  (0) 2026.03.18
C++ 메모 - 은/는, 이/가 자동 처리  (0) 2026.03.17
C++ 기초 - 객체지향 설계  (1) 2026.03.16
C++ 헷갈리는 개념 정리  (0) 2026.03.13
C++ 기초 오답노트 / 기능 정리  (0) 2026.03.12