C++: 디자인 패턴(Design Pattern)
C++: 디자인 패턴(Design Pattern)
소프트웨어 디자인 패턴(Software Design Pattern)
- 소프트웨어 개발에 있어서 반복적인 문제를 해결하기 위한 해법으로서 이미 만들어 놓은 패턴들이다. 짧게 디자인 패턴이라고 부를 때가 더 많다.
- 큰 범주에 따라 3가지 큰 가지로 나뉜다.
생성 패턴(Creational Pattern)
- 새로운 객체를 만드는 것에 관한 패턴이다. 즉 생성에 관여하는 방법을 제시한다.
싱글톤(Singleton)
- 문맥상 오직 하나의 객체만 존재하도록 보장하는 패턴이다.
- 여러 객체로부터 하나의 객체에 접근하고 수정이 빈번히 일어날 때, 대상을 하나로 통일 시키는데 용이하다.
- 전역변수와 유사한 개념이지만 단순 데이터인지, 전역적인 객체에 대한 접근제어인지에 따라 차이점을 가진다.
- 사용 언어와 상황에 따라 여러가지의 구체적인 구현법이 존재하지만, 보통 다음은 공통적으로 들어간다.
- 생성자를 private으로 설정하여 외부에서의 직접적인 생성을 막는다.
- 인스턴스를 static으로 선언하여 클래스 스코프 안에서 1개만 생성되도록 한다.
- Getter를 static으로 선언하여 인스턴스를 얻을 때 오직 하나의 함수만 있도록 보장한다.
- (권장되지만 필요에 따라) 복사 생성자와 대입 생성자를 삭제하여 객체 복사를 막는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
#include <iostream> using namespace std; class BasketBall { private: static BasketBall* Instance; //정적 인스턴스 int BounceTime; BasketBall() { BounceTime = 0; } //연산자 삭제를 통한 복사방지 BasketBall(const BasketBall&) = delete; BasketBall& operator=(const BasketBall&) = delete; public: static BasketBall* GetInstance() { //Getter 함수 if (Instance == nullptr){ Instance = new BasketBall(); } return Instance; } void ShowBounceTime() { //Bounce 출력 함수 cout << BounceTime << endl; } void Bounce() { //Bounce 증가 함수 BounceTime++; } }; BasketBall* BasketBall::Instance = nullptr; //정적 변수 초기화, C++ 17부턴 정적 변수도 선언부에서 초기화 가능 class Player { //농구선수 클래스 public: void BounceBall() { BasketBall::GetInstance()->Bounce(); } }; int main() { Player A; Player B; //A와 B는 동일한 BasketBall을 호출, 총 2 증가 A.BounceBall(); B.BounceBall(); // 2 출력 BasketBall::GetInstance()->ShowBounceTime(); }
구조 패턴(Structural Pattern)
- 객체들의 결합에 관여하는 패턴이다. 연결이 어떤식으로 구성되는지에 대한 방법을 제시한다.
데코레이터(Decorator)
- 기존 클래스의 수정 없이 기능의 추가를 구현하기 위한 패턴으로, 데코레이터가 기존 클래스를 감싸면서(Wrapper class) 기능을 추가하게 된다.
- 상속으로 구현하기엔 클래스가 지나치게 많아지는 경우인 클래스 폭팔(Class Explosion)을 방지하고, 조합을 바탕으로 간결하게 다양한 경우의 수를 감당하지만, 구조가 복잡해지고 객체의 수가 증가하며 디버깅이 어려워 질 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
#include <iostream> #include <string> using namespace std; class Sandwich { //샌드위치 클래스 public: virtual ~Sandwich() {} virtual string GetInfo() = 0; }; class BasicSandwich : public Sandwich { //기본 샌드위치 public: string GetInfo() override { return "Basic BLT\n"; } ~BasicSandwich() { cout << "BasicSandwich destroyed!" << endl; } }; class SandwichDecorator : public Sandwich { //샌드위치 데코레이터 protected: Sandwich* sandwich; public: SandwichDecorator(Sandwich* sandwich) : sandwich(sandwich){} string GetInfo() override { return sandwich->GetInfo(); } ~SandwichDecorator() { cout << "Decorator destroyed!" << endl; delete sandwich; } }; class Ham : public SandwichDecorator {// 햄 토핑 public: Ham(Sandwich* sandwich) : SandwichDecorator(sandwich) {} string GetInfo() override { return sandwich->GetInfo() + ">> +Ham\n"; //햄 추가 } ~Ham() { cout << "Ham destroyed!" << endl; } }; class Cheese : public SandwichDecorator {// 치즈 토핑 public: Cheese(Sandwich* sandwich) : SandwichDecorator(sandwich) {} string GetInfo() override { return sandwich->GetInfo() + ">> +Cheese\n"; //치즈 추가 } ~Cheese() { cout << "Cheese destroyed!" << endl; } }; int main() { Sandwich* A = new BasicSandwich(); Sandwich* B = new Ham(A); Sandwich* C = new Cheese(B); Sandwich* D = new Ham(C); cout << D->GetInfo() << endl; //Basic sandwich, ham, cheese, ham 출력 //순차적으로 해제: Ham -> decorator -> cheese -> decorator //-> ham ->decorator -> basic sandwich delete D; }
행동 패턴(Behavioral Pattern)
- 객체들 간의 상호작용 패턴이다. 상태가 변화할 때 그 변화를 어떻게 다루고 전달할지에 대한 방법을 제시한다. 앞서 STL과 백터에서 보았을 반복자(Iterator)도 행동 패턴의 구현체라고 볼 수 있다.
옵저버(Observer)
- 대상(Subject)를 관찰하는 관찰자(Observer)가 대상으로부터 알림을 받고(Notify) 그에 따른 동작을 하는 패턴이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
#include <iostream> #include <vector> using namespace std; class Observer { //옵저버 클래스 (동작) public: virtual void Update() = 0; }; class DriveThru { //드라이브스루 클래스(대상) private: vector<Observer*> Observers; public: void Attach(Observer* Observer) { //옵저버 추가 함수 this->Observers.push_back(Observer); } void Detach(Observer* target){ // 옵저버 제거 함수 for(auto i = Observers.begin(); i != Observers.end(); i++){ if(*i == target){ Observers.erase(i); return; } } } void notify() { //알림 함수 for (Observer* X : Observers) { X->Update(); } } void EnteringCar() { //알림 트리거 동작 cout << "Order Received!" << endl; notify(); } }; class KitchenRoom : public Observer { //주방 클래스(동작) public: void Update() override { CookFood(); } void CookFood() { cout << "Preparing the Order!" << endl; } }; class Pickup : public Observer { //픽업 카운터 클래스 public: void Update() override { GreetingCustomer(); } void GreetingCustomer() { cout << "Welcome!" << endl; } }; int main() { DriveThru* Gate = new DriveThru(); KitchenRoom* Kitchen = new KitchenRoom(); Pickup* Counter = new Pickup(); //옵저버 부착 Gate->Attach(Kitchen); Gate->Attach(Counter); //Notify Gate->EnteringCar(); //Order Received! => Preparing the Order! => Welcome! //옵저버 제거 Gate->Detach(Counter); //Notify Gate->EnteringCar(); //Order Received! => Preparing the Order! }
- 대상(Subject)를 관찰하는 관찰자(Observer)가 대상으로부터 알림을 받고(Notify) 그에 따른 동작을 하는 패턴이다.
- 지금 소개된 패턴 외에도 팩토리(Factory), 빌더(Builder), 어댑터(Adapter), 파사드(Facade)등 꽤나 많은 패턴들이 존재한다.
- 디자인 패턴 자체를 하나의 언어처럼 다루며 복잡한 문제를 해결하는 의사 코드(Pseudo code) 차원의 설계법으로 접근할 수도 있다.
This post is licensed under CC BY 4.0 by the author.