Design Pattern

From WhiteWing
Jump to: navigation, search

Contents

Creational Patterns

Factory

Abstract Factory : Meta-Factory

Factory Method : Inheritance

Builder Pattern

abstract class PizzaBuilder {
    protected Pizza pizza;
    public Pizza GetPizza() { return pizza; }
    public void CreateNewPizza() { pizza = new Pizza(); }
    public abstract void BuildDough();
    public abstract void BuildSauce();
    public abstract void BuildTopping();
}
class HawaiianPizzaBuilder extends PizzaBuilder;
class SpicyPizzaBuilder extends PizzaBuilder;
class Cook {
    private PizzaBuilder builder;
    public void SetPizzaBuilder(PizzaBuilder builder) { this.builder = builder; }
    public Pizza ConstructPizza() {
        builder.CreateNewPizzaProduct();
        builder.BuildDough();
        builder.BuildSauce();
        builder.BuildTopping();
    }
}

Singleton

// GoF95
class Singleton
{
public:
    static Singleton* GetInstance()
    {
        if ( instance == NULL )
            instance = new Singleton();
        return instance;
    }
protected:
    Singleton() {}
private:
    static Singleton* instance;
};
Singleton* Singleton::instance = NULL;
template<class T>
class Singleton
{
public:
    // Guaranteed to be destroyed.
    // Instantiated on first use.
    static T& GetInstance() { static T instance; return instance; }
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton &) {}; // Don't Implement
    Singleton& operator=(const Singleton<T>&) {}; // Don't Implement
    Singleton* operator&() {};
};
class A : public Singleton<A>
{
friend class Singleton<A>;
private:
    A(); // Should be private
    ~virtual A();
    A(const A&);
    A& operator=(const A&);
    A* operator&();
};
template<class T>
class Singleton
{
public:
    static T* GetInstance()
    {
        if (!instance)
            instance = new T;
        return instance;
    } // delete는 어떻게?
private:
    static T* instance;
};
template<class T> T* Singleton<T>::instance = NULL;
template<typename T> class Singleton
{
public:
  static T & GetInstance();
protected:
  virtual ~Singleton() { }
private:
  static void initialize();
  static void __cdecl finalize();
  static T*volatile instance;
};
template<typename T> T*volatile Singleton<T>::instance = NULL;
template<typename T> T& Singleton<T>::GetInstance()
{
  if (NULL != instance) return instance;
  static volatile long mutex = 0L;
  while (true)
  {
    const long result = ::InterlockedCompareExchange(&mutex, 1L, 0L);
    if ( 0L == result) break;
  }
  if (NULL == instance)
    initialize();
  mutex = 0L;
  return (*instance);
}
template<typename T> void Singleton<T>::initialize()
{
  instance = new T();
  ::atexit(&finalize);
}
template<typename T> void __cdecl Singleton<T>::finalize()
{
  delete instance;
  instance = NULL;
}
  • C++ Singleton Pattern은 그냥 심심풀이로 짜다 보면 거지같게 짜이기 일쑤다. 아직까지 맘에 딱 맞는 코드를 발견하지 못했음. 근데 이건 Thread-Safe하지 않지 않나? 나중에 답변 달길.
  • 한 가지 더. 만일 생성자가 변수를 받아들인다면 어떻게 할 것인가? 여기에 그에 대한 논의가 있긴 하지만, 싱글턴 패턴을 사용하는데 굳이 변수까지 받아 넘겨야 하는지 잘 모르겠다.
  • 싱글턴 패턴은 많은 사람들이 제일 싫어하는 패턴 일순위로 올려두는 패턴이다. 사실상 이것은 전역 변수와 다를 것이 없다. 싱글턴의 생성과 삭제를 조금 더 편리하게 하는 정도랄까. 모두가 싱글턴에 접근할 수 있다는 점은 누가 이 클라스에 의존하고 있는지 추적을 어렵게 만든다. 더군다나 이 클라스를 상속하는 것은 정말 쉽지 않다. 또한 확장성 - 싱글턴이 복수 객체가 될 가능성 - 을 생각해 보면, 이 디자인에 깊이 의존하는 것은 위험하다. 또 한 가지는 Java에서는 진정한 싱글턴이 존재할 수 없다는 것이다. 이에 대해서는 위키 백과를 참조하라.
  • 싱글턴 패턴이 없다면? 최상위 구조에서 객체를 생성한 후 이에 의존하는 모든 객체에 이것을 넘겨주어야 한다. 그것 역시 악몽이다.

Prototype : Clone

Behavioral Patterns

Chain of Responsibility

abstract class Logger {
    public static int ERROR = 3;
    public static int NOTICE = 5;
    public static int DEBUG = 7;
    protected int mask;
    // The next element in the chain of responsibility
    protected Logger next;
    public Logger(int m) { mask = m; }
    public Logger SetNext(Logger l) { next = l; return l; }
    public void Message(String msg, int priority) {
        if (priority <= mask) {
            WriteMessage(msg);
        }
        if (next != null) {
            next.Message(msg, prioity);
        }
    }
    abstract protected void WriteMessage(String msg);
}
class StdoutLogger extends Logger;
class EmailLogger extends Logger;
class StderrLogger extends Logger;
public static void main(String[] args) {
    Logger logger, l;
    l = logger = new StdoutLogger(Logger.DEBUG);
    l = l.SetNext(new EmailLogger(Logger.NOTICE));
    l = l.SetNext(new StderrLogger(Logger.ERROR));
    logger.Message("Entering function f.", Logger.DEBUG);
    logger.Message("Step 1 is completed.", Logger.NOTICE);
    logger.Message("An error has occurred.", Logger.ERROR);
}

Command

Interpreter

Iterator

// C#
// explicit version
IEnumerator<T> iter = list.GetEnumerator();
while (iter.MoveNext()) Console.WriteLine(iter.Current);
// implicit version
foreach (var item in list) Console.WriteLine(item);
// Java
// explicit version
Iterator iter = list.iterator();
while (iter.hasNext()) System.out.println(iter.next());
// implicit version
for (Item item : list) System.out.print(item);
# Python
for item in list:
    print item
std::vector<int>::iterator it;
for (it = v.begin(); it != v.end(); ++it) { cout << *it << end; }
  • C#이나 Java는 이렇게나 간단하고 우아한데 C++은 왜 이렇게 더러운지. 쯥. 하지만 C++0x의 auto가 들어간다면 어떨까?
  • 중간에 객체가 컬렉션에 추가나 삭제가 될 때에는 이 패턴의 사용에 신중을 기해야 한다. 커서가 컬렉션의 시작과 끝의 범위 안에서 정확히 동작해야 안전한 구현이 가능하다. 멀티스레드 환경에서는 커서의 범위가 수시로 간섭 당할 수 있으므로 탐색 전에 범위의 간섭이 일어나지 않도록 해야 한다.
    • 근데 사실 이건 Linked List, Array 등등의 모든 Collection에 다 적용되는 말이긴 하네. 물론 그냥 Iterate만 할 때도 커서가 하나 뿐이라서 간섭이 일어나는 경우는 있으니 (Read만 해도 Thread-Safe 하지 않을 경우가 있다!), 더욱 각별히 요주의 해야 할 듯.
  • Related to : Visitor Pattern

Mediator

Memento

Observer

// Modified from the following site: 
// http://www.codeproject.com/KB/architecture/observer_with_templates.aspx
template <class T>
class Observer
{
public:
    // 이렇게 해야 한 객체가 여러 Subject의 Observer가 될 수 있다.
    virtual void Notify(T* subject) = 0;
};
template <class T>
class Subject
{
public:
    virtual ~Subject() { assert(m_observers.empty()); }
    void AddObserver(Observer<T>* o) { m_observers.insert(o); }
    void RemoveObserver(Objerver<T>* o) { m_observers.erase(o); }
    void NotifyObservers() {
        std::set<Observer<T>*>::iterator it;
        for (it = m_observers.begin(); it != m_observers.end(); it++)
            (*it)->Notify(static_cast<T*>(this));
    }
private:
    std::set<Observer<T>*> m_observers;
};
// Copied and modified from the following site:
// http://www.drdobbs.com/184403873
class Subject
{
public:
    void AddObserver(function<void (Subject*)> o) { m_observers.insert(o); }
    void RemoveObserver(function<void (Subject*)> o) { /* ... */ }
    void NofiyObservers() {
        list<function<void (Subject*)> >::iterator it;
        for (it = m_observers.begin(); it != m_observers.end(); it++)
            (*it)(this);
private:
    std::list<function<void (Subject*)> > m_observers;
};
class Observer
{
public:
    Observer(Subject* s) : m_subject(s) {
        m_subject->AddObserver( bind1st( mem_fun( &Subject::Update ), this ) );
    }
    virtual ~Observer() {
        m_subject->RemoveObserver( bind1st( mem_fun( &Subject::Update ), this ) );
    }
private:
    Subject* m_subject;
};
  • Observer는 삭제되기 전에 RemoveObserver를 반드시 불러야 한다. 그렇지 않으면 Subject가 널 객체에 Update 함수를 부르게 될 것이다. Subject가 Observer보다 먼저 삭제되는 경우는 어떻게? 그러한 상황 자체가 일어나면 안 된다!
  • 두 번째 안은 Callback 함수를 사용해서 구현된 패턴이다. 본문에서는 다음과 같은 장점이 있다고 한다.
    • Subject는 굳이 Observer를 inherit 하지 않은 클래스들에게도 신호를 줄 수 있다.
    • Update Function 안에 임의의 argument를 쑤셔 넣어주고 cast해서 사용할 수 있다.

하지만 개인적으로 이 둘은 안정성/가독성을 심각하게 떨어뜨리는 명백한 단점이다. C에서 Callback을 부르던 가락이 남았을 뿐이라고 생각함. std::set이 왜 여기에서는 std::list 인지는 본문을 찾아볼 것.

State

class Machine {
    class State *m_state;
public:
    Machine() : m_state(NULL) {} // Initialize m_state if desired
    virtual ~Machine { delete m_state; }
    void SetState(State* state) { delete m_state; m_state = state; }
    void On() { m_state->On(this); }
    void Off() { m_state->Off(this); }
};
class State {
public:
    virtual void On(Machine* m) {};
    virtual void Off(Machine* m) {};
};
class OnState : public State {
public:
    void Off(Machine* m) {
        m->SetState(new OffState());
        //delete this; // 오호라!
    }
};
  • State의 삭제를 어디서 하여야 할까? State의 내부에서 하는 것보다는 Machine::SetState 에서 처리해 주는 것이 더 자연스럽게 보인다. State의 생성과 소멸 위치가 서로 다르다는 것, Machine의 생성과 소멸 시 초기 State의 생성과 마지막 State의 소멸 역시 또 다르다는 것이 약간 마음에 걸린다.
  • 상태 객체를 싱글턴으로 만드는 경우도 종종 있다. 이런 경우는 보통 변수는 공유하지 않고 코드만 사용하는 경우. 공유 정보는 일반적으로 함수 인자를 통해 넘겨준다. 위의 코드에서는 Machine*을 넘겨 받음으로써 정보를 얻을 수 있을 것이다. 음... 그럼 그냥 static class를 바로 넘겨버리는 방법은 없나?
  • Related to : Template Method Pattern

Strategy

void qsort(void* base, size_t num, size_t, size, int(*comparator)(const void*, const void*));
  • 알고리즘을 독립적으로 확장시킬 수 있는 방법이다. 무거운 객체를 상속을 사용하지 않아도 된다.
  • 그렇다면 위의 qsort의 경우처럼 함수 포인터만 넘겨주어도 될 것이다. 아니면 static function, static class, Singleton instance 같은 것이 아무래도 가볍게 알고리즘을 갈아 끼울 수 있는 방법 아닐까.

Template Method

bool CDocument::Save()
{
    if ( !InternalSaveBefore() )
        return false;
    bool ret = InternalSave();
    if ( !InternalSaveAfter(ret) )
        return false;
    return true;
}
  • Hook. 많은 개발자들이 부모 클래스에서 미리 구조를 정의한다는 말에 어려움을 느끼는 모습을 보인다. 부모 클래스는 자연히 동작의 구조를 정의하게 되고 특화된 동작은 자식 클래스가 가지게 되는 것이다. 플래그를 남용하여 도저히 분해할 수 없는 코드를 작성하기 보다는 객체지향의 다형성을 활용하여 언제라도 다시 합칠 수 있는 코드를 만드는 것이 바람직한 방법이다. - 패턴 그리고 객체지향적 코딩의 법칙, 문우식, 한빛미디어
  • Related to : State Pattern

Visitor

  • 탐색 시 계층 구조로 탐색을 하면 재귀적 호출 많아져서 스택 과부하가 걸릴 수 있으므로 Visitor 패턴을 사용하라는 말은 신빙성이 있는가? 계층 구조가 그렇게 무지막지한 깊이를 가질 수 있을까?
  • Related to : Iterator Pattern

Null Object

Structural Patterns

Adaptor

호환성을 가지지 않는 인터페이스를 맞추기 위한 목적으로 사용된다. 안정성이 확보된 코드를 DIY(Do It Yourself) 정신에 입각한답시고 분해하는 순간 힘들게 얻은 안정성은 깨어지고 만다.

Bridge

Composite

  • 객체들의 트리로 대변되는 객체들 사이의 소유 구조를 동일한 인터페이스로 다룰 수 있는 방법이다. 개별 객체와 복합 객체를 코드상에서 따로 구분하지 않고 이들의 공통점으로 만들어진 인터페이스를 구성하는 것이 이 패턴의 핵심이다. 개별 객체 역시 자식을 가지지 않는 복합 구조로 이해하는 것이 편하다.
  • 다음 패턴들과 깊은 연관이 있다.
    • Iterator, Command, Chain of Responsibility, Interpreter
    • Builder, Decorator, Flyweight, Visitor

Decorator

  • 사실 이것은 Proxy Pattern과 매우 흡사하다. 무엇이 다른가? 본 객체와의 관계가 컴파일 시에 정해지냐 아니냐의 차이가 있다. 그 말은 Decorator는 또 다른 Decorator를 감쌀 수도 있다는 말. 그렇다면 Proxy는 그냥 Decorator의 특수한 경우 중 하나라고 부를 수 있을 것인가? 그런 듯 하다. 다만 Proxy는 Decorator처럼 너무 사용되는 혼잡을 피하기 위하여 딱 한 번만 감쌀 수 있도록 강제한 구조 정도라고 보면 좋을 듯 하다.

Facade

Flyweight

Proxy

class IControlLayer {
public:
    virtual void Connect() = 0;
    virtual void SendData() = 0;
    virtual void Close() = 0;
};
class CControlLayer : public IControlLayer { // 본 객체
    /* ... */
};
// 하지만 어떤 객체는 데이터를 보낼 때 마다 매번 연결을 열고 끊어 주길 원한다.
class CConnectionlessControlProxy : public IControlLayer {
public:
    void Connect() {}
    void SendData() {
        CControlLayer controlLayer;
        controlLayer.Connect();
        controlLayer.SendData();
        controlLayer.Close();
    }
    void Close() {}
};
class IPainter {
public:
    virtual void Draw() = 0;
};
class CPainter : public IPainter { /* ... */ };
class CPainterProxy : public IPainter {
    IPainter* m_painter;
public:
    CPainterProxy() : m_painter(NULL) {}
    void Draw() { // Lazy initialization
        if (!m_painter)
            m_painter = new CPainter();
        m_painter->Draw();
    }
};
  • 어떻게 객체의 책임을 덜 수 있을까? 매니저(=Proxy)를 두면 된다. 매니저가 적절한 스케줄 관리도 해 주고 아직 본 객체가 무대에 나설 준비가 되지 않았다면 그에 대한 대응도 할 수 있을 것이다. 하지만 다른 객체들이 원하는 것은 Proxy가 아니라 본 객체이다. 다른 객체들이 Proxy와 본 객체를 일일이 구분해서 다룬다면 Proxy를 둔다고 해도 복잡도가 감소하지 않는다. 이를 해결하게 하기 위하여 Proxy 객체가 본 객체와 동일한 인터페이스를 구현해서 다른 객체들이 Proxy 객체가 마치 본 객체인 것처럼 접근하게 한다. - 318p, 문우식, 패턴 그리고 객체지향적 코딩의 법칙

References

Personal tools