본문 바로가기
정리글

디자인패턴에 대한 사용을 위한 정리

by lch831009 2019. 3. 23.

designpatternscard.pdf



Case별로 작성된 illustrer_pattern 자료.pdf



pattern들간의 비교 : 

https://hamait.tistory.com/869



내용참조 : https://realzero0.github.io/study/2017/06/12/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC.html




정리를 하고싶은데... 

꽤 이미지랑 이런것들이 많이 필요함. 


GoF(Gang of Four)에서는 23가지 디자인 패턴을 3가지 유형으로 분류, 결국은 Design Pattern을 사용할때 얻어지는 benefit이 제일 중요함. benefit을 보면서 그 benefit을 고려할때 얻는것이 많으면 반드시 그 pateern을 사용하게 된다. 하지만 benefit이 없으면 사용하지 않게 된다. 


요약

A. Creational Pattern

객체를 생성하는데 관련된 패턴들

Factory method, Abstract factory, builder, prototype, singleton


B. Structural Pattern

프로그램 구조에 관련된 패턴들

Adapter, Adapter(object), Bride, Composite, Decorator, Facade, Flyweight, Proxy


C. Behavioral Pattern

상호작용을 패턴화 해놓은 것들

Interpreter, Template method, Chain of Responsibility, Command, iterator, mediator, memento, observer, state, strategy, visitor












구체적인 사용용도




1. 어댑터 패턴(Adapter Pattern), ok

용도

어떤 클래스를 우리가 바로 사용할 수 없을 때가 있다. 다른 곳에서 개발한 클래스고, 우리가 그것을 수정할 수 없을 때, 우리에게 맞게 중간에 변환할 역할을 해줄 수 있는 클래스가 필요한데, 그것이 바로 어댑터이다.

사용 방법

상속

위임: 어떤 메소드의 실제 처리를 다른 인스턴스의 메소드에게 맡기는 방법



Class Diagram


아래의 Client는 Adapter만 알면 되며, Adapter는 virtual operation()를 만들어 놓아야 된다.

ConcreteAdapter는 operation()을 구현한다. 

ConcreteAdapter는 Adaptee instance를 가지고 있는다.

ConcreteAdapter는 operation()을 호출당할때 Adaptee의 adaptedOperation()을 호출하여 Client가 원하는 operation() 작업을 수행한다.


































2. 프로토타입 패턴(Prototype Pattern), ok


용도

미리 만들어진 객체를 복사해서 객체를 생성하는 방식

객체를 많이 만들어야 할 경우, 객체 생성에 드는 코딩 분량을 현저히 줄일 수 있다

클래스로부터 객체를 생성하기 어려운 경우(그래픽 에디터에서 사용자의 마우스 클릭으로 생성되는 객체들)


사용 방법

모형(Prototype) 인스턴스를 등록해 놓고, 등록된 인스턴스를 복사(clone())해서 인스턴스를 생성함


Class Diagram 








객체 생성과 관련된 패턴들은 서로 영역이 겹치는 면이 있다. 프로토타입 패턴과 추상 팩토리 패턴 중 어느 하나가 적용될 수 있는 경우가 있다. 추상 팩토리 패턴이 프로토타입들의 집합을 갖고있다가, 클론(clone)한 뒤 프로덕트(product) 객체를 반환할 수도 있다. 추상 팩토리 패턴, 빌더 패턴, 프로토타입 패턴은 각 구현에 있어서 싱글턴 패턴을 활용할 수 있다. 다시 말해 추상 팩토리 클래스는 종종 팩토리 메소드과 함께 구현하거나, 프로토타입을 이용해서 구현되기도 한다. 보통 설계는 처음에는 팩토리 메소드로 출발한다. 다음에 설계자의 재량에 따라 추상 팩토리 패턴, 빌더 패턴, 프로토타입 패턴으로 바뀔 수 있다. 프로토타입은 서브클래싱을 필요로 하지 않는다. 하지만 "초기화" 동작을 필요로 한다. 팩토리 메서드 패턴은 서브클래싱을 필요로 하나, "초기화" 동작은 필요로 하지 않는다. 설계자는 컴포지트 패턴이나 데코레이터 패턴을 매우 많이 사용한 무거운 설계를 프로토타입을 사용하여 더 좋게 만들 수 있다. 원칙은 "런타임"에 또 다른 "객체"를 생성한다는 것이다. 다시 말해 이 시점에 가서 클로닝(cloning)을 하는 객체의 "실제 복사본"이 만들어지는 것이다. "실제 복사본"이라는 말은 새로 생성되는 객체가 클로닝(cloning) 당하는 객체의 애트리뷰트와 똑같은 애트리뷰트를 가질 것이라는 말이다. 반면에, "new"를 이용해 객체를 생성했다면, 새로이 생성된 객체의 애트리뷰트들은 초기값을 가질 것이다. 


https://ko.wikipedia.org/wiki/프로토타입_패턴


프로토타입 패턴의 장점

객체를 생성해 주기 위한 별도의 객체 생성 클래스가 불필요하다.

객체의 각 부분을 조합해서 생성되는 형태에도 적용 가능하다.


프로토타입 패턴의 단점

생성될 객체들의 자료형인 클래스들이 모두 clone() 메서드를 구현해야 한다.


sample code


#include <iostream>

#include <vector>

#include <algorithm>

#include <functional>


using namespace std;

const int N = 4;


// Prototype

class Document 

{

public:

   virtual Document* clone() const = 0;

   virtual void store() const = 0;

   virtual ~Document() { }

};



// Concrete prototypes : xmlDoc, plainDoc, spreadsheetDoc


class xmlDoc : public Document 

{

public:

   Document*   clone() const { return new xmlDoc; }

   void store() const { cout << "xmlDoc\n"; }

};


class plainDoc : public Document 

{

public:

   Document* clone() const { return new plainDoc; }

   void store() const { cout << "plainDoc\n"; }

};


class spreadsheetDoc : public Document 

{

public:

   Document* clone() const { return new spreadsheetDoc; }

   void store() const { cout << "spreadsheetDoc\n"; }

};


// makeDocument() calls Concrete Portotype's clone() method 

// inherited from Prototype

class DocumentManager {

public:

   static Document* makeDocument( int choice );

   ~DocumentManager(){}


private:

   static Document* mDocTypes[N];

};


Document* DocumentManager::mDocTypes[] = 

{

   0, new xmlDoc, new plainDoc, new spreadsheetDoc

};


Document* DocumentManager::makeDocument( int choice ) 

{

   return mDocTypes[choice]->clone();

}


// for_each op ()

struct Destruct

{

    void operator()(Document *a) const { 

delete a; 

    }

};


// Client

int main() 

{

   vector<Document*> docs(N);

   int choice;

   cout << "quit(0), xml(1), plain(2), spreadsheet(3): \n";

   

    while (true) 

    {

  cout << "Type in your choice (0-3)\n";

  cin >> choice;

      if (choice <= 0 || choice >= N)

         break;

      docs[choice] = DocumentManager::makeDocument( choice );

   }


   for (int i = 1; i < docs.size(); ++i) 

   if(docs[i]) docs[i]->store();


   Destruct d;

    // this calls Destruct::operator()

    for_each(docs.begin(), docs.end(), d);


   return 0;

}
















3. 싱글톤 패턴(Singleton Pattern)


용도

* 시스템 내부에 1개의 인스턴스만 생성하고 싶은 경우, 단하나의 객체만을 생성하게 강제하는 패턴.

* 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다.

* 안드로이드 앱 같은 경우 각 액티비티나 클래스별로 주요 클래스들을 일일이 전달하기가 번거롭기 때문에 싱글톤 클래스를 만들어 어디서나 접근하도록 설계하는 것이 편하기 때문...

* 인스턴스가 절대적으로 한개만 존재하는 것을 보증하고 싶을 경우 사용.

* 두 번째 이용시부터는 객체 로딩 시간이 현저하게 줄어 성능이 좋아지는 장점!



문제 - thread일경우, create단계에서 race가 발생할수 있음. 그 경우 여럿이 생성될수 있음.



사용 방법

생성자를 private으로 선언하고, 해당하는 생성자를 클래스 내부에서만 호출함


Class Diagram 















4. 컴포지트 패턴(Composite Pattern)

http://hajeonghyeon.blogspot.com/2017/04/design-pattern-dpattern.html

정의 - 주어진 상황 및 용도에 따라 객체에 추가적인 책임을 덧붙이는 패턴. 서브클래스를 통해 기능을 유연하게 확장하는 방법을 제공한다. 상속을 통해 구성요소의 형식을 맞춰서 감싸 사용하는 일종의 래퍼(Wrapper).  기존의 컴포넌트에 원래 있던 메소드를 호출하기 전, 또는 후에 데코레이터에서 별도의 작업을 처리하는 방식으로 기능을 추가한다. 데코레이터의 형식은 그 데코레이터로 감싸는 객체의 형식과 같다. 상속을 이용해서 형식을 맞추는 것. 인스턴스로 본래 컴포넌트를 가지고 있고, 또 그 자신도 컴포넌트와 같은 형식이므로 다른 데코레이터로 감쌀 수도 있다. 


Component, Decorator : Component

ConcreteComponent : Component를 객체로써 사용. 이를 ConcreteDecorator : Decorator로 장식 가능. ConcreteDecorator는 Component 형식이므로 또 다른 Decorator로 장식 가능.


예시 :

Component c = new OtherDecorator(new SomeDecorator(new ConcreteComponent));

c.Do();

//ConcreteComponent.Do()->SomeDecorator.Do()->OtherDecorator.Do() 순서로 호출.


 기존 컴포넌트의 메소드가 A를 출력한다고 했을 때, 데코레이터로 감싸서 기존 컴포넌트의 함수를 호출한 뒤, B를 출력하게끔 한다. 그러면 컴포넌트 형식으로 해당 객체를 불러서 메소드를 실행 했을 때에, AB가 출력될 것이다. 계속 덧붙여서 ABCDEF 이런 식으로 행동을 추가할 수도 있다. 형식은 어디까지나 기존 컴포넌트 형식이다.

 자바의 I/O가 데코레이터 패턴으로 구현되어 있다. FileInputStream으로 파일을 하나씩 읽고, BufferedInputStream으로 FileInputStream에서 읽은 내용을 버퍼링하는 식이다.











5. 데코레이터 패턴(Decorator Pattern)

용도

데코레이팅한 결과물을 다시 내용물로 보고 그것을 다시 데코레이팅하기 위함(지속적으로 장식을 추가할 때, 문자열 주위에 여러 유형의 border 장식을 추가할 때)

사용 방법

Border 클래스가 다시 Display를 포함함(컴포지트랑 비슷)

Class Diagram // Decorator가 Component를 포함하고 있음







6. 퍼사드 패턴(Facade Pattern)

용도

대규모 프로그램에는 서로 관련있는 클래스들이 많음 -> 복잡하게 얽혀있는 클래스들을 정리해서 높은 레벨의 인터페이스(API)를 제공(간단하게 접근가능)

여러 클래스들을 직접 제어하지 않고 ‘창구(facade)’에 요구함

결과적으로 구현시에 간단한 인터페이스를 사용할 수 있게

사용 방법

여러 클래스들의 기능들을 묶은 Facade 클래스를 만들고 Facade 클래스에 접근함

Class Diagram 












7. 프록시 패턴(Proxy Pattern)

용도

Proxy는 대리인이라는 의미, 시간이 많이 걸리는 작업을 할 때 사용함

시간이 많이 걸리는 작업을 할 때, 대리인이 할 수 있는 일은 대리인이 하고 할 수 없는 일(Heavy job)은 본래의 클래스에게 넘겨줌

시스템 초기화는 필요하지 않은 기능까지 초기화하려고 하면 많은 시간이 필요한데, 그 기능을 Proxy에 위임함

프린트 프로그램에서 실제 프린터를 실행하는 과정에 시간이 오래 걸리기 때문에 그 과정에 있는 일들을 Proxy에 위임함

사용 방법

Proxy클래스에 우선 일을 위임하고, 그 뒤에 RealSubject가 해야할 일은 넘겨주는 방법으로 사용

Class Diagram 









8. 옵저버 패턴(Observer Pattern)


용도

관찰 대상의 상태가 변화했을 때 관찰자에게 통지하는 패턴

상태 변화에 따른 처리를 기술할 때 효과적으로 활용(MVC패턴에서 Model과 View의 분리 등)

사용 방법

Observer 클래스에 상태 변화를 알려주고, Observer는 다시 그 변화에 맞는 결과를 나타냄



Class Diagram 





구현 및 Code 














9. 커맨드 패턴(Proxy Pattern)

http://hajeonghyeon.blogspot.com/2017/04/blog-post.html


정의 : 요청을 객체 형태 캡슐화하는 것. 사용자가 보낸 요청을 나중에 이용할 수 있도록 메소드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 만든 패턴 명령 패턴은 메서드 호출을 실체화한 것이다. 명령 패턴은 콜백을 객체지향적으로 표현한 것. 요청을 하는 객체와 그 요청을 수행하는 객체를 분리한다. 특정 객체에 대한 특정 작업 요청을 캡슐화한다. 요청 내역을 큐에 저장하거나 로그로 기록할 수 있다. 실행된 작업을 저장해뒀다가 실행취소도 가능하다. 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있다. 외부에서 볼 때 자신이 요청한 명령이 실제로 어떻게 처리되는지 알 수 없게 캡슐화. 그냥 execute 메소드만 호출하면 요구사항이 처리되도록 한다.  Command는 실제로 명령을 수행할 Receiver 객체를 가지고 있다. Command에서 Execute()만 구현해두면 요청하는 쪽에서는 상세를 알 필요가 없다. Execute() 내에서 Receiver 객체를 통해 작업을 수행한다.  Invoker는 Client로부터 전달받은 Command 객체를 통해 명령을 발동한다. Action에 따른 일련의 Command 객체들을 가지고 있을 수도 있다. 필요에 따라 명령 발동 기록을 남긴다.

 Receiver는 Command의 Execute()에서 하달받은 명령을 실제로 수행한다.  Client는 어느 시점에 어떤 명령을 수행할지 결정한다. 명령을 수행하려면, Client 객체는 Invoker 객체로 Command 객체를 전달해야 한다. 



Command, Receiver, Invoker, Client


Command 객체는 Receiver를 가지고 있고, Receiver의 메소드를 호출한다.(Execute(), Undo())

Receiver는 자신에게 정의된 메소드를 수행한다.

Command 객체는 Invoker 객체에 전달되어 명령을 발동하게 된다.

Invoker 객체는 필요에 따라 명령 발동에 대한 기록을 남길 수도 있다.

하나의 Invoker에게 다수의 Command 객체가 전달될 수도 있다.

Client 객체는 어느 시점에서 어떤 명령을 수행할지 결정한다.

명령을 실행하기 위해 Client 객체는 Invoker 객체로 Command 객체를 전달한다.

Invoker는 일련의 커맨드 객체를 가지고 있고, 요청이 들어왔을 때 요청받은 커맨드를 수행한다.


Command로 일련의 다른 Command들을 수행하면 여러 작업을 처리하는 매크로를 만들 수도 있다.

실행한 Command를 Invoker의 스택에 쌓아두면 Undo가 구현 가능.

실행한 Command를 Invoker의 큐에 쌓아두면 로그 구현 가능.

요청받은 Command를 Invoker의 큐에 쌓아두면 메시지 큐 등 구현 가능.


플레이어가 제어하는 캐릭터 뿐 아니라 같은 방식으로 다른 액터(AI)도 제어할 수 있다. AI 엔진과 AI 액터 사이의 인터페이스로 커맨드 패턴을 사용하면 된다. 예시2.


예시:

SomeCommand : Command { Execute(); Undo(); }

Invoker { Command[] slot; Stack<Command> history; SetCommand(slotNum, Command);  OnSomeAction(){Command[actionNum].Execute();}; }

Receiver { do something requested from Execute() }

Client { Invoker.SetCommand(SomeCommand(SomeReceiver)); Invoker.Action() }


예시2:


class JumpCommand : Command {

public:

    virtual void execute(GameActor& actor){

        actor.jump();

    }

};


Command* command = inputHandler.handleInput();

if(command) command->execute(actor);


Command* InputHandler::handleInput(){

    if(isPressed(BUTTON_X)) return buttonX;

}


















10. 책임 연쇄 패턴(Chain of Responsibility Pattern)


용도


어떤 요구가 발생했을 때, 그 요구를 처리할 Object를 바로 결정할 수 없을 때, 다수의 Object를 Chain으로 연결해 차례로 방문하면서 목적에 맞는 Object를 결정함(내가 못하면 남한테 전가시킴)

요구하는 측과 처리하는 측의 연결을 약화시킴(Coupling을 낮추는 역할을 함)

사용 방법

Handler객체가 문제를 해결했는지 확인하면서 계속해서 가능한 객체를 연결해 줌

Class Diagram 














11. 중재자 패턴(Mediator Pattern)


용도

모든 행동을 수행하기 전에 ‘중재자 객체’의 결정이 있어야 하고, 중재자 객체로 프로그램이 수행됨

각 객체들은 중재자만 알게됨

사용 방법

각 객체와 중재자를 연결함

Class Diagram 














12. 방문자 패턴(Visitor Pattern)


용도

데이터와 메소드를 구분하기 위함

많은 데이터에 여러 가지 유형의 처리를 수행할 경우 활용

사용 방법

데이터 구조 내부를 traversal하는 ‘visitor’ 클래스로 그 클래스에게 데이터의 처리를 맡김, 새로운 처리를 추가할 때는 새로운 visitor를 생성함

Class Diagram 













13. 팩토리 메소드 패턴(Factory Method Pattern)


용도

Instnce 작성을 하위 class에게 위임

Instance를 만드는 방법은 상위 class에서 결정하지만, 구체적인 class명을 결정하지 않음

Instance 생성을 위한 Framework과 실제 Instance를 생성하는 Class를 분리함

객체를 만드는 공장을 만드는 패턴 -> 결합도가 낮아질 수 있다

사용 방법

Factory객체를 만들고 Factory에서 객체들을 생성한다





내용 출처 : https://showmiso.tistory.com/117


캡슐화, 바뀌는 부분과 바뀌지 않는 부분을 분리하고 바뀌는 부분을 묶어두는것.

팩토리 패턴에서는 생성을 묶어 놓음. (묶는다는 말은 모아놓다는 의미)


Pizza OrderPizza(char* pType)

{

Pizza m_pizza;


// 피자 생성

if(strcmp(pType,"치즈"))

{

m_pizza = new CheesePizza();

}

else if(strcmp(pType,"햄"))

{

m_pizza = new HamPizza();

}

else if(strcmp(pType,"파인애플"))

{

m_pizza = new PinePizza();

}


// 준비

m_pizza.Prepare();


// 굽기

m_pizza.Bake();


// 자르기

m_pizza.Cut();


// 포장

m_pizza.Package();


return m_pizza;

}


위 코드에서 피자 생성부분을 보겠습니다. 메뉴를 추가하거나 삭제할 때 이 부분이 주로 변경되고, 그 외의 부분은 변경되지 않습니다.


변경되는 부분과 변경되지 않는 부분을 알았으니 변경되는 부분을 캡슐화 해보겠습니다.



class SimplePizzaFactory

{

public:

Pizza CreatePizza(char* pType)

{

Pizza m_pizza;


// 피자 생성

if(strcmp(pType,"치즈"))

{

m_pizza = new CheesePizza();

}

else if(strcmp(pType,"햄"))

{

m_pizza = new HamPizza();

}

else if(strcmp(pType,"파인애플"))

{

m_pizza = new PinePizza();

}


return m_pizza;

}

};


class PizzaStore

{

private:

SimplePizzaFactory factory;

public:

PizzaStore(SimplePizzaFactory _factory)

{

factory = _factory;

}

Pizza OrderPizza(char* pType)

{

Pizza m_pizza;


// 피자 생성

m_pizza = factory.CreatePizza(pType);


// 준비

m_pizza.Prepare();


// 굽기

m_pizza.Bake();


// 자르기

m_pizza.Cut();


// 포장

m_pizza.Package();


return m_pizza;

}

};



SimplePizzaFactory 클래스에서 하는 일은 피자를 생성하는 일 뿐입니다.


이렇게 피자를 생성하는 작업을 한 클래스에 캡슐화 시켜놓으면 어떤 구현을 변경해야 하는경우 이 팩토리 클래스만 변경하면 되기 때문에 보다 변화에 열려있는 코드가 됩니다.


PizzaStore 클래스를 보면 생성자에 팩토리 객체 전체가 전달됩니다.  생성 부분에서는 그냥 팩토리를 써서 피자 객체를 만드는 것을 볼 수 있습니다.


PizzaStore 클래스의 주문 과정은 모든 피자가게에서 동일하게 이루어진다고 할 때, 달라지는 것은 계속 말했듯이 생성입니다.


PizzaStore의 서브 클래스(자식 클래스)를 만들어 그 내부에서 CreatePizza() 를 구현한다면 자식 클래스마다 다른 스타일의 피자를 생성할 수 있게됩니다. 만약 새로운 스타일의 피자를 추가 생성하고 싶다면, 클래스를 추가하면되고, 삭제하고 싶다면 클래스를 삭제하면 되는 것입니다.


// PizzaStore.h

#pragma once


class Pizza;


// 피자 가게

class PizzaStore

{

private:

// 피자 생성

virtual Pizza* CreatePizza(char* pType);

public:

// 피자 주문

Pizza* OrderPizza(char* pType);

};


// 뉴욕 피자 가게

class NYPizzaStore : public PizzaStore

{

private:

Pizza* CreatePizza(char* pType);

};


// 시카고 피자 가게

class ChicagoPizzaStore : public PizzaStore

{

private:

Pizza* CreatePizza(char* pType);

};



// PizzaStore.cpp

#include <iostream>

#include "Pizza.h"

#include "PizzaStore.h"


using namespace std;


// 피자 가게

// 피자 생성

Pizza* PizzaStore::CreatePizza(char* pType)

{

Pizza* pizza = NULL;

return pizza;

}

// 피자 주문

Pizza* PizzaStore::OrderPizza(char* pType)

{

Pizza* pizza = NULL;

// 피자 주문하는 곳에서 피자를 생성하게 한다.

pizza = CreatePizza(pType);


// 준비

pizza->Prepare();

// 굽기

pizza->Bake();

// 커팅

pizza->Cut();

// 포장

pizza->Package();


return pizza;

}


// 뉴욕 피자 가게

Pizza* NYPizzaStore::CreatePizza(char* pType)

{

Pizza* pizza = NULL;


if(!strcmp(pType,"치즈"))

{

pizza = new NYStyleCheesePizza;

}

else if(!strcmp(pType,"햄"))

{

pizza = new NYStyleHamPizza;

}

else if(!strcmp(pType,"조개"))

{

pizza = new NYStyleClamPizza;

}

else if(!strcmp(pType,"파인애플"))

{

pizza = new NYStylePinePizza;

}

else

{

cout <<"* 메뉴가 없습니다."<< endl;

}


return pizza;

}


// 시카고 피자 가게

Pizza* ChicagoPizzaStore::CreatePizza(char* pType)

{

Pizza* pizza = NULL;


if(!strcmp(pType,"치즈"))

{

pizza = new ChicagoStyleCheesePizza;

}

else if(!strcmp(pType,"햄"))

{

pizza = new ChicagoStyleHamPizza;

}

else if(!strcmp(pType,"조개"))

{

pizza = new ChicagoStyleClamPizza;

}

else if(!strcmp(pType,"파인애플"))

{

pizza = new ChicagoStylePinePizza;

}

else

{

cout <<"* 메뉴가 없습니다."<< endl;

}


return pizza;

}



 OrderPizza 함수에서는 Pizza 객체를 가지고 여러 작업을 하지만 실제로 어떤 클래스에서 작업이 처리되고 있는지는 전혀 알 수가 없습니다. 이 말은 즉 PizzaStore와 Pizza클래스가 완전히 분리되있는 것을 말합니다.







14. 경량 패턴(Flyweight pattern)

출처 : http://hajeonghyeon.blogspot.com/2017/05/blog-post.html


정의 : 공유를 통해 많은 수의 소립 객체들을 효과적으로 지원한다.


사용 방법

 많은 인스턴스가 공유하는 정보는 하나의 클래스로 만들고, 인스턴스별로 달라야 하는 상태값만 저장하는 클래스를 만들어서 공유 정보 클래스를 참조한다. 하나의 공유 정보 클래스와 무수한 인스턴스를 생성하면 중복된 정보를 메모리에 올릴 필요가 없어진다. 예를 들어 풀을 그리는데 풀의 텍스쳐와 메시를 공유 정보에 두고, 높이와 넓이처럼 인스턴사마다 다른 상태값만 따로 저장하고 공유 정보는 참조로 구성한다. Direct3D, OpenGL 모두 이런 인스턴스 렌더링(Instance rendering)으로 지원한다. 모든 객체의 데이터 값이 같아서 공유할 수 있는 데이터인 '고유 상태(Intrinsic state)'와 인스턴스 별로 값이 다른 '외부 상태(extrinsic state)' 또한 예시로서 지형 타일을 예로 들 수 있는데, 지형 속성은 고유 상태로 두고 하나의 인스턴스를 참조하고, 타일의 위치는 외부 상태로 만들어서 배열을 형성할 수도 있다. 이렇게 하면 지형 타일을 만드는 World는 지형의 세부 정보와 커플링되지 않으며, 타일 속성은 지형 타일 객체에서 참조하는 고유 상태를 통해 알아낼 수 있다. 열거형을 통해 스위치문을 만들 것이라면 경량 패턴을 고려해볼 수 있다.



예시:


class World

{

    private:

        Terrain* tiles[WIDTH][HEIGHT];

}


void World::generateTerrain()

{

    for(int i=0;i<WIDTH;i++){

        for(int j=0;j<HEIGHT;j++){

            tiles[i][j] = &hillTerrain; or tiles[i][j] = &grassTerrain;

        }

    }

}


그냥 i,j라는 좌표의 타일이 어떤 terrain이라는 것만 표시한다. 실제로 저 정보를 중복해서 담는 타일들을 생성하지는 않는다.







15. 스테이트 패턴(State pattern)


http://hajeonghyeon.blogspot.com/2017/04/state-pattern.html


정의 : 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다. 스테이트 객체로 일련의 행동을 캡슐화하고 이를 컨텍스트 객체에서 상태에 따라 갈아낀다. 그러면 컨텍스트 객체는 자신의 상태에 따라 스테이트 객체의 행동을 하게 된다.  상태를 기반으로 하는 행동을 캡슐화하고, 행동을 현재 상태한테 위임한다. 상태 전환은 스테이트 클래스에서 제어할 수도 있고, 컨텍스트 클래스에서 제어할 수도 있다.  스트래티지 패턴과 같은 방식으로 행동을 상속해서 사용한다. 스테이트 패턴은 상황에 따라 컨텍스트(Context) 객체에서 여러 스테이트 객체 중 하나가 내부 상태를 나타내고, 이 스테이트 객체에 따라 컨텍스트 객체의 행동도 자연스럽게 바뀐다. 클라이언트는 상태 객체에 대해 몰라도 상관 없다.  이 점에서 스트래티지 패턴과의 차이가 있다. 스트래티지 패턴을 사용할 때에는 클라이언트에서 컨텍스트 객체에게 어떤 전략(Stratege) 객체를 사용할지를 지정해준다. 일반적으로 스트래티지 패턴은 서브클래스를 만드는 대신 행동을 상속하여 유연성을 극대화시키기 위해 쓰인다. 스트래티지 패턴을 사용하면 구성을 통해 행동을 정의하는 객체를 유연하게 바꿀 수 있다. 바꿔 쓸 수 있는 행동을 캡슐화한 다음, 실제 행동은 다른 객체에 위임한다. 스테이트 패턴은 상태 객체를 바꾸는 것만으로 컨텍스트 객체의 행동을 바꿀 수 있다. 애니메이션 상태 제어, 유닛의 상태변경(공격<->대기<->이동 등) 등에 쓰일 수 있다. 유한 상태 기계(FSM)을 구현하는 좋은 방법이다.



예시:

State { Handle() }

IdleState : State { Handle(){ IdleAnimation.Play }

MoveState : State { Handle() { MoveAnimation.Play }

Context { Handle(){ currentState.Handle(); }


Client.Context.SetState(MoveState) } // Additional behavior can be executed when state changed.

//State changed. Thus context behavior changed.

Client.Context.Handle(); // Context executes the behavior according to current state