
드디어 디자인 패턴 부분이다. 디자인 패턴을 잘 알고있으면 어떻게 코드를 설계하는게 좋은지, 또 어떠한 방식으로 하는게 효율적인지 등등 여러 도움되는 내용들을 얻을 수 있다. 패턴의 종류는 엄청 다양해서 모든걸 다 다루지는 못한다.
반복적으로 발생하는 문제들에 대해 적합한 설계 솔루션을 정의한 것
- 패턴 사용 장점: 전문가들의 경험을 바탕으로 한거임
-> 솔루션에 대한 논쟁 제거(이미 선대에 다함)
-> 개발 생산성 증가
객체의 생성과정을 효율적으로 하기 위한 패턴 및 솔루션
관련 있는 여러 객체들을 일관된 방식으로 생성할 수 있게 해주는 패턴
enum Themes {
THEME_LIGHT,
THEME_DARK
};
Themes g_currentTheme = THEME_LIGHT;
class TitleBar;
class LightThemeTitleBar : public TitleBar {};
class DarkThemeTitleBar : public TitleBar {};
class CloseButton;
class LightThemeCloseButton : public CloseButton {};
class DarkThemeCloseButton : public CloseButton {};
void Window::OnCreate() {
if (g_currentTheme == THEME_DARK) {
m_titleBar = new DarkThemeTitleBar();
m_closeButton = new DarkThemeCloseButton();
} else {
m_titleBar = new LightThemeTitleBar();
m_closeButton = new LightThemeCloseButton();
}
}
만약에 이렇게 생성시, 각 UI의 테마색을 바꾸는 코드가 있다고 해보자.
아무생각 없이 짜게 되면 각 버튼, 패널마다 변수를 확인해 밝은색 또는 검은색으로 구현을 할것이다. 하지만... 당연하게도 새로운 색이 추가가되거나 새로운 UI요소가 생기게 되면 무수히 많은 코드를 수정해야 한다. 이것은 아무도 원치 않을 것이다.
// 위젯 부분
class TitleBar;
class CloseButton;
class WidgetFactory {
public:
virtual TitleBar *CreateTitleBar() = 0;
virtual CloseButton *CreateCloseButton() = 0;
};
extern WidgetFactory *g_widgetFactory;
widgetFactory를 만들고, 외부 노출할 부분만 한다.
*g_widgetFactory를 외부로 선언해 어디서는 호출할 수 있게 해준다.
// widget.cpp
class LightTitleBar : public TitleBar {};
class DarkTitleBar : public TitleBar {};
class LightCloseButton : public CloseButton {};
class DarkCloseButton : public CloseButton {};
class LightWidgetFactory : public WidgetFactory {
public:
TitleBar *CreateTitleBar() {
return new LightTitleBar();
}
CloseButton *CreateCloseButton() {
return new LightCloseButton();
}
};
class DarkWidgetFactory : public WidgetFactory { ,,, 이 부분에 내용 추가,,,};
// 테마가 선택되는 것에 따라 이 factory 를 바꿔준다.
WidgetFactory *g_widgetFactory = new LightWidgetFactory();
void Window::OnCreate() {
m_titleBar = g_widgetFactory->CreateTitleBar();
m_closeButton = g_widgetFactory->CreateCloseButton();
}
<사용처>
복잡한 객체를 단계별로 생성할 수 있도록 도와주는 패턴
class User {
public:
User(string name, int age) {}
User(string name, int age, string id) {}
};
vector<User *> readUserFile(vector<string> lines) {
vector<User *> users;
for (l : lines) {
User *u;
list<string> fields = splitField(l);
if (fields.size() == 2) {
string name = fields[0];
int age = atoi(fields[1].c_str());
u = new User(name, age);
} else if (fields.size() == 3) {
string name = fields[0];
int age = atoi(fields[1].c_str());
string id = fields[2];
u = new User(name, age, id);
}
users.push_back(u);
}
return users;
}
만약에 이렇게 생성자가 여러개 있는 경우가 있다. 이러면 필드의 개수를 새서 2개면 name,age로 반환하고 3개면 name, age, id를 가진 객체를 생성한다.
하지만 단점이 눈에 보인다. 일단 케이스가 2개밖에 없는데 복잡해서 눈에 잘 읽히지 않는다. 또한 더 많은 생성자가 계속 등장하게 되면 점점 복잡해질 것이다.

class User {
public:
User(string name, int age) {}
User(string name, int age, string id) {}
};
class UserBuilder {
private:
string m_name;
int m_age;
string m_id;
public:
void setName(string name) {m_name = name;}
void setAge(int age) {m_age = age;}
void setId(string id) {m_id = id;}
User *build() {
if (m_id.empty()) {
return new User(m_name, m_age);
} else {
return new User(m_name, m_age, m_id);
}
}
};
vector<User *> readUserFile(vector<string> lines) {
vector<User *> users;
for (l : lines) {
UserBuilder *builder = new UserBuilder;
list<string> fields = splitField(l);
string name = fields[0];
builder->setName(name);
int age = atoi(fields[1].c_str());
builder->setAge(age);
if (fields.size() >= 3) {
string id = fields[2];
builder->setId(id);
}
User *u = builder->build();
users.push_back(u);
}
return users;
}
이해를 위해 게임으로 간략하게 예시를 들어보겠다.
만약에 유저 객체를 생성하려고 하면
Character c = new Character("Arthur", "Warrior",
"Sword", "Shield", "Berserk", "Wolf");
// 너무 길고, 순서 틀리면 버그나기 쉬움
이렇게 되어있으면 누가봐도 힘들다 하지만!
Character c = new Character.Builder("Arthur")
.job("Warrior")
.weapon("Sword")
.skill("Berserk")
.pet("Wolf")
.build();
이런식으로 필요한 부분만 그때끄때 추가하면 빌더인 것이다.
객체를 생성할 때 어떤 클래스를 생성할지 서브클래스에서 결정하는 것.
-> 직접 new키워드를 사용하지 않고 "생성을 담당하는 메서드"가 생성한다.
카페에서 “디저트 주세요!” 하면
케이크 가게는 케이크를 주고
아이스크림 가게는 아이스크림을 준다.
// Product
abstract class Dessert {
abstract void eat();
}
class Cake extends Dessert {
void eat() { System.out.println("케이크 냠냠"); }
}
class IceCream extends Dessert {
void eat() { System.out.println("아이스크림 찬찹"); }
}
// Creator
abstract class DessertFactory {
abstract Dessert createDessert();
}
// Concrete Creators
class CakeFactory extends DessertFactory {
Dessert createDessert() {
return new Cake();
}
}
class IceCreamFactory extends DessertFactory {
Dessert createDessert() {
return new IceCream();
}
}
// 클라이언트 코드
public class Main {
public static void main(String[] args) {
DessertFactory factory = new CakeFactory(); // 상황에 따라 교체 가능
Dessert dessert = factory.createDessert();
dessert.eat();
}
}
미리 만들어둔 객체를 복제해서 객체를 생성
new를 사용하지 않고 이미 있는 애들 복제해서 사용한다.
class Shape implements Cloneable {
private String color;
public Shape(String color) {
this.color = color;
}
@Override
public Shape clone() {
try {
return (Shape) super.clone(); // 메모리 복사
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void setColor(String color) {
this.color = color;
}
public String toString() {
return "Shape with color: " + color;
}
}
// 3. 클라이언트 코드
public class Main {
public static void main(String[] args) {
Shape original = new Shape("red");
Shape copy = original.clone();
copy.setColor("blue");
System.out.println(original); // red
System.out.println(copy); // blue
}
}
한 클래스에 객체가 오직 하나만 존재하는 것을 보장하기 위해 사용한다.
class Singleton {
public:
static Singleton* GetInstance();
private:
static Singleton* s_instance;
Singleton();
};
Singleton* Singleton::s_instance = NULL;
Singleton* Singleton::GetInstance () {
if (s_instance == NULL) {
s_instance = new Singleton;
}
return s_instance;
}
이렇게 전역으로 Singleton객체를 가지는 전역 변수를 만들고, 이걸 호출해서 유일하게 만들면 되는 것이다. 참고로 싱글톤 패턴은 게임개발에서 필수적으로 사용되는 중요한 기법이다.
GameManager, SystemManager등등 자주 사용하게 된다.