디자인 패턴을 4개 정도 소개할 예정.
행위 패턴 - 템플릿 메소드 패턴 (Template Method Pattern)
틀을 미리 만들어 놓는 것. 비슷한 기능을 수행하는 클래스가 여러 개일 때
1. 동일한 기능을 수행하는 메소드는 상위클래스(추상클래스)에서 정의하고,
2. 서로 다른 기능을 수행하는 메소드는 하위 클래스에 구현하는(hook) 패턴이다.
변하는 부분들은 나중에 구현하는 방식.
실습 진행
Template Method 패턴을 적용하여 커피를 만들어보자.
1. 다양한 종류의 커피가 있다.
- 아메리카노, 카페라떼, 카푸치노, 카페모카...
2. 커피 제조 과정은 다음과 같다. (makeCoffee)
- 에스프레소를 넣는다. (공통 기능)
- 추가 재료를 넣는다. (커피 종류에 따라 다름)
- 커피가 나온다. (공통 기능)
#pragma once
#include <string>
#include <iostream>
using namespace std;
class Coffee
{
string m_name;
public:
Coffee(string s) : m_name(s) {}
void makeCoffee()
{
void putEspresso();
void putExtra();
void takeout();
}
void putEspresso();
void takeout();
virtual void putExtras() = 0;
};
class Americano : public Coffee
{
public:
Americano(string s) : Coffee(s) {}
void putExtras() override
{
cout << "Add Water";
}
};
class CaffeLatte : public Coffee
{
public:
CaffeLatte(string s) : Coffee(s) {}
void putExtras() override
{
cout << "Add Milk";
}
};
class TemplateMethod
{
public:
void Start()
{
Americano* americano = new Americano("merimericano");
CaffeLatte* caffelatte = new CaffeLatte("lalalatte");
americano->makeCoffee();
caffelatte->makeCoffee();
}
};
정리
1. 템플릿 메소드 패턴은 유사한 알고리즘으로 구성된 클래스를 구현할 때 적합한 행위 패턴입니다.
2. 추상클래스에서는 동일한 메소드를 정의하고 -> Template method
3. 확장이나 변화가 필요한 메소드는 하위클래스에서 구현한다. -> hook
4. 템플릿 메소드 패턴을 사용함으로써 코드의 중복을 제거할 수 있다.
생성 패턴 - 팩토리 메소드 패턴 (Factory Method Pattern)
팩토리 메소드 패턴은 객체를 생성하는 인터페이스가 있지만,
실제 객체 생성은 서브클래스에 위임하는 생성패턴이다.
유사한 속성을 가진 객체들이 다양할 때 유용한 패턴이다.
Simple Factory Pattern
Factory Method Pattern을 알아보기 전에 Simple Factory Pattern을 먼저 알아보자.
간단한 예제로, 음료수를 만드는 공장이 있고. 음료수는 커피, 콜라, 사이다 등 다양하다.
#pragma once
#include <string>
using namespace std;
struct Drink
{
Drink() {}
virtual string drinkInfo() = 0;
};
enum drinkType
{
COFFEE,
COKE,
SPRITE,
};
class Drinkfactory
{
public:
virtual Drink* makeDrink(drinkType type) = 0;
{
switch(type)
{
case COFFEE:
return new Coffee();
case COKE:
return new Coke();
case SPRITE:
return new Sprite();
default:
return nullptr;
}
return nullptr;
}
};
팩토리 메소드 패턴식으로 바꾸면..
#pragma once
#include <string>
using namespace std;
struct Drink
{
Drink() {}
virtual string drinkInfo() = 0;
};
struct Coffee : public Drink
{
string drinkInfo() override
{
return "Coffee";
}
};
enum drinkType
{
COFFEE,
COKE,
SPRITE,
};
class DrinkFactory
{
public:
DrinkFactory() {}
virtual Drink* makeDrink() = 0;
};
class CoffeeFactory : public DrinkFactory
{
public:
Drink* makeDrink() override
{
return new Coffee();
}
};
class CokeFactory : public DrinkFactory
{
};
class SpriteFactory : public DrinkFactory
{
};
Case study : 프랜차이즈 피자 가게
1. 피자가게는 여러 개의 프랜차이즈 지점을 가지고 있다.
2. 피자 주문은 각 지점에서 처리된다. 피자를 만드는 과정은 비슷하다.
3. 피자를 만드는 과정은 비슷하다.
4. 각 지점별로 피자스타일이 다르다.
5. 새로운 스타일의 피자를 동적으로 추가할 수 있다.
지점별로, 다양한 피자를, 만들기 어려운 문제.
피자의 종류에 따라 별도의 클래스에서 객체를 생성하도록 하자.
각각의 피자는 CPizza 추상 클래스(인터페이스)로부터 상속받아 구현한다.
피자를 추가, 삭제 시 코드 수정이 필요하다.
변하는 부분과 변하지 않는 부분을 분리하여 Template Method Pattern형태로 처리한다.
피자 객체는 각 지점에서 만들도록 처리하자. createPizza만 만들어주면 되게끔 구성 할 수 있다.
정리
팩토리 메소드 패턴의 장점
- 객체 추상화 단계가 높아진다
- 객체 생성은 팩토리 메소드에 캡슐화
- 사용자는 생성하는 객체에 대해 알 필요가 없다.
- 객체 추가/ 삭제가 용이하다
팩토리 메소드 패턴의 단점
추상화클래스를 상속해서 필요한 객체들을 클래스로 구현해야 하기 때문에 코드가 증가한다.
생성 패턴 - 프로토타입 디자인 패턴 (Prototype Design Pattern)
기존 객체를 복제(Cloning)하여 새로운 객체를 생성하는 패턴.
Unity에서 Prefab과 같이 객체를 복제해서 사용하는 것과 같은 디자인 패턴이다.
동적(run time)으로 필요할 때 마다 복제하여 사용할 수 있다.
Prototype Design Pattern example
캔 공장이 있다.
캔 공장에서는 콜라 캔과 사이다 캔을 만든다.
유저가 필요한 캔을 요청하며 동일한 속성을 갖는 캔을 복사해서 제공한다.
#pragma once
#include <string>
#include <map>
using namespace std;
enum CANTYPE
{
COKE,
SPRITE
};
struct Can
{
virtual Can* clone() = 0;
virtual void draw() = 0;
};
class SpriteCan : public Can
{
string m_name;
public:
SpriteCan(string n) : m_name(n) { }
~SpriteCan() {}
Can* clone()
{
return new SpriteCan(*this);
}
void draw() {
cout << m_name << endl;
}
};
class CokeCan : public Can
{
string m_name;
int m_price;
public:
CokeCan(string n, int p) :
m_name(n), m_price(p) { }
~CokeCan() {}
Can* clone()
{
return new CokeCan(*this);
}
void draw() {
cout << m_name << endl;
cout << m_price << endl;
}
}
};
class CanFactory
{
unordered_map<CANTYPE, Can*> m_cans;
public:
CanFactory()
{
m_cans[COKE] = new CokeCan("Coke", 3000);
m_cans[SPRITE] = new SpriteCan("Sprite");
}
~CanFactory() {}
Can* createClone(CANTYPE ct)
{
return m_cans[ct]->clone();
}
};
}
Prototype Design Pattern example2
게임에서 무기를 구매하고자 한다.
무기 판매점에서 유저가 원하는 무기를 요청하면 해당되는 무기를 제공한다.
단, 동일한 무기는 같은 스펙을 제공한다.
정리
- 기존 객체를 복제하여 새로운 객체를 생성하는 패턴
- 동적으로 필요할 때마다 복제하여 사용할 수 있다.
- 복사 생성자를 이용하여 객체를 생성함으로써 생성된 객체들의 속성은 동일하다.
- 프로토타입이 미리 정의되어 있기 때문에 중복되는 객체 초기화(생성자) 코드를 제거할 수 있다.
- 미리 필요한 객체를 생성한 후 복제하여 사용하므로 객체 생성 비용을 줄일 수 있다.
생성 패턴 - 싱글톤 패턴 (Prototype Design Pattern)
인스턴스(객체)가 하나만 만들어지고, 어디서든 그 인스턴스에 접근할 수 있도록 하는 패턴이다.
단일객체를 필요한 타이밍에 생성하는 방식.
- 유일무이한 객체 (One and only one instance)
- 글로벌 접근이 가능 (Global access)
- 객체 소유자가 없음 (No ownership)
- 지연 초기화 (처음 객체가 필요할 때까지 초기화 지연)
전역변수에 비해 필요할 때만 만들 수 있어 자원 절약 가능하고,
객체를 중복 생성할 위험이 없다.
Singleton Pattern 구현
생성사를 통한 생성을 막아라 : Private constructor
해당 클래스의 포인터 멤버 변수를 내부에 하나 생성 : Static member
정적 멤버 함수를 만들어라 : Static function
class Singleton
{
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance()
{
if (singleInstance == null)
singleInstance == new Singleton();
return singleInstance;
}
};
빠르게 4가지 디자인 패턴에 대해 살펴보았다.
개발을 할 때 어떤 패턴으로 만들면 좋을 것 같다는 그림, 감이 그려져야 함.
다음 주는 FSM과 관련하여 학습 할 예정.
'대학생활 > 수업' 카테고리의 다른 글
레벨디자인심화 3주차 - 영화를 기반으로 게임 플레이 구성 (0) | 2023.09.14 |
---|---|
리얼타임엔진 3주차 - 시작해요 UEFN2 (0) | 2023.09.12 |
게임밸런스및시뮬레이션 3주차 - 성장 밸런스 & 시뮬레이터 (0) | 2023.09.11 |
게임그래픽프로그래밍심화 3주차 - 그래픽스 API 초기화 및 렌더링 (0) | 2023.09.11 |
게임배경음악과효과음 2주차 - 게임 사운드 기획 (0) | 2023.09.07 |