오늘은 디자인 패턴에 대한 수업을 들었다.
1. 디자인 패턴
1.1 정의
- 디자인 패턴은 개발하면서 발생하는 반복적인 문제들을 어떻게 해결할 것인지에 대한 해결 방안 중 모범 사례이다.
- 디자인 패턴은 객체 지향 4개 특성 (캡슐화, 상속, 추상화, 다형성)과 설계 원칙(SOLID)을 기반이다.
간단히 말하면, 클래스를 설게 할 때 참고할 수 있는 구조적 예시이다.
하지만 우리 C++의 경우, 일반적인 디자인 패턴을 가지고 오면 메모리 누수가 발생하기에 프로그래머가 수동으로 메모리 누수를 잡아야 한다.
1.2 스트래티지 패턴 vs 스테이트 패턴
스트래티지 패턴
- 정의: 알고리즘을 캡슐화하여 실행 중에 교체할 수 있도록 설계하는 패턴.
- 특징: 외부에서 알고리즘(전략)을 선택하고 변경합니다.
- 사용 예: 사용자가 직접 무언가를 변경(예: "날 수 있는 기능"으로 교체) 해야 하는 경우.
- 핵심 포인트: 외부에서 결정된 전략으로 행동이 바뀝니다.
스테이트 패턴
- 정의: 객체의 상태를 캡슐화하여, 상태에 따라 객체의 동작이 자동으로 변경되도록 설계하는 패턴.
- 특징: 객체의 내부 상태가 스스로 변하며 동작도 변경됩니다.
- 사용 예: "이 상태가 되면 행동이 자동으로 변해야 한다"는 경우.
- 핵심 포인트: 내부 상태 변화에 따라 행동이 바뀝니다.
[차이점 정리]
스트래티지 스테이트
주체 | 외부에서 행동 방식을 선택 | 내부 상태 변화에 따른 자동 행동 변경 |
행동 변경 방식 | 사용자가 직접 교체 | 상태 변화에 따라 내부에서 자동 변경 |
주요 사례 | "날기", "헤엄치기" 기능을 교체 | "잠", "깨어남" 상태에 따른 행동 변화 |
- 스트래티지 패턴: 외부에서 "이제 이걸로 바꿔라." (ex: 업그레이드 시 사용자 명령으로 교체)
- 스테이트 패턴: 내부에서 "상태가 바뀌었으니 이 동작으로 변경." (ex: 상태 변화에 따라 자동으로 행동 변경)
1.3 데코레이터 패턴
- 정의 :데코레이터 패턴은 객체를 특수 래퍼 객체에 감싸서 새로운 기능을 추가하거나 확장할 수 있도록 하는 구조적 디자인 패턴
- 이 패턴은 중복 허용이 가능하며, 기존 클래스의 수정 없이 기능을 동적으로 확장할 수 있다
위에 UML을 구현해 보겠다.
1.3.1 구현
Beverage.h
- 음료의 기본 인터페이스(I_BEVERAGE)와 추상 클래스(C_BEVERAGE)를 정의
#pragma once
#include "stdio.h"
__interface I_BEVERAGE
{
void print();
int getCost();
};
class C_BEVERAGE abstract : public I_BEVERAGE
{
private:
public:
C_BEVERAGE() = default;
virtual ~C_BEVERAGE() = default;
};
Decaf.h / cpp
- 기본 음료 클래스 ( Decaf )
#pragma once
#include "beverage.h"
class C_DECAF : public C_BEVERAGE
{
public:
void print() override;
int getCost() override;
};
#include "decaf.h"
void C_DECAF::print()
{
printf("디카페인\n");
}
int C_DECAF::getCost()
{
return 600;
}
Espresso.h / cpp
- 기본 음료 클래스 ( Espresso )
#pragma once
#include "beverage.h"
class C_ESPRESSO : public C_BEVERAGE
{
public:
void print() override;
int getCost() override;
};
#include "espresso.h"
void C_ESPRESSO::print()
{
printf("에스프레소\n");
}
int C_ESPRESSO::getCost()
{
return 500;
}
Decorator.h / cpp
- 데코레이터는 음료 객체(C_BEVERAGE)를 포인터로 참조하며, 새로운 기능을 추가
#pragma once
#include "beverage.h"
class C_DECORATOR abstract : public C_BEVERAGE
{
protected:
C_BEVERAGE* m_pBeverage;
public:
C_DECORATOR(C_BEVERAGE* pBeverage);
~C_DECORATOR();
};
#include "decorator.h"
C_DECORATOR::C_DECORATOR(C_BEVERAGE* pBeverage) :
m_pBeverage{}
{
m_pBeverage = pBeverage;
}
C_DECORATOR::~C_DECORATOR()
{
delete m_pBeverage;
}
- C_BEVERAGE* m_pBeverage :
핵심으로 decorator가 beverage를 알고 있어야 함, 그렇기에 생성자에서 생성할 때 꽂아줘야 한다 - 그리고 생성자에서 생성을 했으니 주기를 맞추기 위해 소멸자에서 delete를 하여 메모리 누수를 막음
Milk.h / cpp ( milk, mocha, whip 구조가 똑같아 하나만 적어놓음 )
#pragma once
#include "decorator.h"
class C_MILK : public C_DECORATOR
{
public:
C_MILK(C_BEVERAGE* pBeverage);
void print() override;
int getCost() override;
};
#include "milk.h"
C_MILK::C_MILK(C_BEVERAGE* pBeverage) :
C_DECORATOR(pBeverage)
{
}
void C_MILK::print()
{
m_pBeverage->print();
printf("우유\n");
}
int C_MILK::getCost()
{
return m_pBeverage->getCost() + 400;
}
main.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
#include "decaf.h"
#include "espresso.h"
#include "milk.h"
#include "mocha.h"
#include "whip.h"
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
C_BEVERAGE* pBeverage = new C_ESPRESSO{};
pBeverage = new C_MILK(pBeverage);
pBeverage = new C_MOCHA(pBeverage);
pBeverage = new C_MOCHA(pBeverage);
pBeverage = new C_WHIP(pBeverage);
pBeverage = new C_WHIP(pBeverage);
pBeverage->print();
printf("%d\n", pBeverage->getCost());
delete pBeverage;
}
1.3.2 동작 원리
생성:
- 음료 객체(예: C_ESPRESSO)를 생성
- 데코레이터 객체(예: C_MILK)는 생성자로 기존 객체를 받아 내부에 저장.
연쇄 연결:
- 각 데코레이터는 원본 객체를 포함하며, 새로운 기능을 추가
- 데코레이터끼리 연쇄적으로 연결
소멸:
- delete를 호출하면, 소멸자가 재귀적으로 호출되며 모든 객체가 해제
'C++' 카테고리의 다른 글
[강의] 1월 2일 수업정리 (0) | 2025.01.02 |
---|---|
[강의] 12월 31일 수업정리 (0) | 2024.12.31 |
[강의] 12월 27일 수업정리 (0) | 2024.12.27 |
[강의] 12월 26일 수업정리 (1) | 2024.12.26 |
[강의] 12월 24일 수업정리 (0) | 2024.12.25 |