
디자인 패턴이란 개발 시 반복적으로 등장하는 문제를 해결하기 위한 일반화된 솔루션
클래스 구조 + 객체 관계 + 책임 분리 방법
유지 보수성, 확장성, 재사용성을 높이기 위한 설계 도구
패턴의 종류는 아래와 같이 구성

생성 패턴(Creational Patterns)
새로운 것을 만들어 내는 방법과 관련된 패턴
공장에서 물건(객체)을 찍어내는 것과 유사한 방식
구조 패턴(Structual Patterns)
여러 부품을 어떻게 조립하고 연결하는 방법에 대한 패턴
여러 개의 객체들의 구조를 어떻게 구성할지가 해당 패턴의 주 관심사
행동 패턴(Behavioral Patterns)
부품이 서로 어떻게 상호작용할지에 대한 패턴
특정 객체가 변할 때 다른 객체들에 이 상태를 어떻게 전달할지 다루는 패턴
싱글톤 패턴 구현
다음과 같은 요구사항에 대해서 구현
비행기 게임 제작
게임 내 제어하는 비행기는 반드시 하나만 존재
비행기 관리
비행기 이동
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), postionY(0){
cout << "Airplane Created at (" <<postionX << positionY <<")"<<endl;
}
public:
/*
복사 생성자와 대입 연산자를 삭제하여 복사 방지
Airplane b=(a);(복사) , Airplane c(a);(복사 대입) 와 같은 것들 사용 못하게 막는 것
이미 있는 객체에 덮어쓰는 것 혹은 새로 만들면서 복사하는 상황 방지
*/
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;
postionY += deltaY;
cout <<"Airplane moved to (" <<positionX << ","<<postionY <<")"<<endl;
}
//현재 위치 출력
void getPosition() const {
cout<<"Airplane Position: ("<<positionX <<","<<positionY<<")"<<endl;
}
};
//정적 멤버 초기화
Airplane* Airplane::instance = nullptr;
//메인 함수(사용 예시)
int main(){
//유일한 비행기 인스턴스를 가져옴
//Airplane 객체 생성된 것에 접근하는 것이 아니라 클래스에 접근하는 방식이어서 :: 사용
Airplane* airplane = Airplane::getinstance();
airplane->move(10,20); //비행기 이동
airplane->getPosition();
//또 다른 요청도 같은 인스턴스를 반환 (sameAirplane이랑 airplane 같은 인스턴스)
Airplane* sameAirplane = Airplane::getInstance();
sameAirplane->move(-5,10); //비행기 이동
sameAirplane->getPosition();
return 0;
}
데코레이터 패턴 구현
문제 상황
피자를 만들려고 합니다.
이때 피자의 토핑을 원하는 대로 추가하면서 동적으로 피자를 만들 수 있어야 합니다.
다음과 같은 요구사항에 대해서 구현
기본 피자
토핑 추가
1️⃣피자에 토핑(치즈, 페퍼로니, 올리브)를 동적으로 추가할 수 있습니다. 2️⃣ 토핑은 이름과 추가 비용을 가집니다.최종 피자의 이름(기본 피자 + 추가된 토핑들)과 가격을 제공합니다.
#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; //순수 가상 함수, 피자의 가격 반환
};
/*
구체 컴포넌트 : BasicPizza
기본 피자 클래스
피자의 기본 베이스(이름과 가격)을 구현
*/
class BasicPizza : public Pizza {
public:
string getName() const {
return "Basic Pizza"; //기본 피자 이름
}
double getPrice() const {
return 5.0; // 기본 피자 가격
}
}
/*
데코레이터 추상 클래스 : PizzaDecorator
기존 피자의 기능을 확장하기 위한 데코레이터의 기본 구조를 정의
내부적으로 Pizza 객체를 감싸며, 이름과 가격에 추가적인 기능을 제공
*/
class PizzaDecorator : public Pizza {
protected:
Pizza* pizza; //기존의 피자 객체를 참조
public:
//데코레이터는 피자 객체를 받아서 감쌈(내부 피자 객체에 대한 소유권을 확보하기 위해서 포인터로 처리)
PizzaDecorator(Pizza* p) : pizza(p) {}
//소멸자에서 내부 피자 객체를 삭제
virtual ~PizzaDecorator(){
delete pizza; //Pizza 클래스 객체 포인터로 선언된 Pizza삭제함으로써 내부 피자 개체가 삭제되는 것으로 처리
}
}
/*
구체 데코레이터 : Cheese, Pepperoni, Olive
각각의 토핑 데코레이터는 PizzaDecorator를 상속받아 이름과 가격을 확장
*/
//치즈 토핑 데코레이터
class CheeseDecorator : public Pizza Decorator {
public:
CheeseDecorator(Pizza* p) : PizzaDecorator(p) {
string getName const{
//기존 피자의 이름에 " + Cheese"를 추가
retrun 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 {
//기존 피자의 이름에 " + Pepporni"를 추가
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(){
//매 라인마다 pizza가 가리키는 객체 바뀌 이전 객체는 새 객체 안으로 포장됨
//런타임에 반복된므로 정적 객체로는 불가능한 구조
//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;
}
옵저버 패턴 구현
문제 상황
엑셀 프로그램을 만들려고 합니다.
엑셀 데이터값을 변경할 때 해당 데이터로 생성한 모든 차트의 데이터가 반영되게 하고 싶습니다.
차트의 개수나 종류가 많을 때 매번 각 차트의 데이터를 수동으로 업데이트하는 것은 비효율적입니다.
어떻게 하면 이를 자동으로 할 수 있을까요?
아래와 같은 요구사항을 받았다고 해봅시다.
엑셀 시트와 차트 관리
상태를 관리하고, 변경되었음을 옵저버에게 알립니다.
2️⃣ ObserverSubject를 관찰하며 상태 변경 시 반응합니다.
Observer들은 Subect에 등록하고 변경이 발생하면 알람을 받습니다.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
/*
행동 패턴 중 옵저버 패턴 구현 예제
객체 상태 변화가 발생하면 등록된 객체에 자동으로 알리는 패턴
Observer 인터페이스
Observer 패턴에서 상태 변화를 알림받는 객체들의 공통 인터페이스
Observer들은 이 인터페이스를 구현하여 'update' 메서드를 통해 데이터를 전달받음
*/
class Observer{
public:
virtual ~Obeserver() = 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->updat(data); //각 Observer에게 데이터를 전달
}
}
//데이터 설정 메서드
//데이터를 변경하고 변경 사항을 모든 Observer에게 알림
void setData(int newData){
data = newData; //새로운 데이터로 갱신
cout << "ExcelSheet: Data updated to "<<data<<endl; //Observer들에게 알림
}
};
//구체적인 Observer 클래스 : BarChart(막대 차트)
class BarChart : public Observer{
public:
void update(int data){ //데이터 업데이트 시 호출됨
cout<<"BarChar: 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;
}
OCP원칙이 적용된 Animal 클래스에 기능 추가
숙제 설명
💡 OCP 원칙은 소프트웨어는 확장에는 열려 있고 수정에는 닫혀 있어야 한다는 원칙입니다. 제시된 코드를 수정하지 않고 새로운 기능을 추가해 보세요.기본 코드
#include <iostream>
#include <vector>
using namespace std;
// 기본 Animal 클래스 (수정 금지)
class Animal {
public:
virtual void speak() = 0; // 순수 가상 함수
virtual ~Animal() = default; // 가상 소멸자
};
// Dog 클래스 (이미 구현됨)
class Dog : public Animal {
public:
void speak() {
cout << "Woof! Woof!" << endl;
}
};
// Cat 클래스 (이미 구현됨)
class Cat : public Animal {
public:
void speak() {
cout << "Meow! Meow!" << endl;
}
};
// 동물의 소리를 출력하는 함수 (수정 금지)
void makeAnimalsSpeak(vector<Animal*> &animals) {
for (Animal* animal : animals) {
animal->speak();
}
// 동적으로 할당된 메모리를 해제합니다.
for (Animal* animal : animals) {
delete animal;
}
}
int main() {
vector<Animal*> animals;
animals.push_back(new Dog());
animals.push_back(new Cat());
makeAnimalsSpeak(animals);
return 0;
}
Animal 클래스와 몇 가지 동물 클래스가 이미 구현되어 있습니다.
새 동물 클래스를 추가해서 동물의 소리를 출력해 보세요.
(어떤 동물을 추가하는지는 자유입니다. 요구사항만 만족하시면 됩니다.)
#include <iostream>
#include<vector>
using namespace std;
// 기본 Animal 클래스 (수정 금지)
class Animal{
public:
virtual void speak()=0; //순수 가상 함수
virtual ~Animal() = default; //가상 소멸자
};
class Dog : public Animal{
public:
void speak(){
cout<<"Woof! Woof!"<<endl;
}
};
class Cat : public Animal{
public:
void speak(){
cout<<"Meow! Meow!"<<endl;
}
};
class Tiger : public Animal{
public:
void speak(){
cout<<"Kuwang! KuWang!"<<endl;
}
};
// 동물의 소리를 출력하는 함수 (수정 금지)
void makeAnimalsSpeak(vector<Animal*> &animals) {
for (Animal* animal : animals) {
animal->speak();
}
// 동적으로 할당된 메모리를 해제합니다.
for (Animal* animal : animals) {
delete animal;
}
};
int main(){
vector<Animal*> animals;
animals.push_back(new Dog());
animals.push_back(new Cat());
animals.push_back(new Tiger());
makeAnimalsSpeak(animals);
return 0;
}