※ Strategy Pattern

전략패턴은 객체들의 행위를 각 전략 클래스에 생성, 유사행위를 캡슐화하는 인터페이스를 정의

객체의 행위를 동적으로 바꾸고 싶다면 전략만 바꿔줌으로 행위를 유연하게 확장하는 방법

클라이언트와 독립적으로 구현되기에 새로운 알고리즘 추가,변경이 쉬워진다.

[구조 예시]

 

#include <iostream>
using namespace std;

class SortBehavior {
public:
    virtual void sort() const = 0;
};
class Merge: public SortBehavior {
public:
    virtual void sort() const { cout << "Merge sort()" << endl; }
};
class Quick: public SortBehavior {
public:
    virtual void sort() const { cout << "Quick sort()" << endl; }
};
class Heap: public SortBehavior {
public:
    virtual void sort() const { cout << "Heap sort()" << endl; }
};

class SearchBehavior {
public:
    virtual void search() const = 0;
};
class Sequential: public SearchBehavior {
public:
    virtual void search() const { cout << "Sequential search()\n"; }
};
class BinaryTree: public SearchBehavior {
public:
    virtual void search() const { cout << "BinaryTree search()\n"; }
};
class HashTable: public SearchBehavior {
public:
    virtual void search() const { cout << "HashTable search()\n"; }
};

// Context
class Collection {
private:
    SortBehavior *m_sort;
    SearchBehavior *m_search;
public:
    Collection(){}
    void set_sort(SortBehavior *s) { m_sort = s; }
    void set_search(SearchBehavior *s) { m_search = s; }
    void sort() const { m_sort->sort(); }
    void search() const { m_search->search(); }
};


int main(int argc, char *argv[])
{
    Merge merge;
    Quick quick;
    Heap heap;

    Sequential sequential;
    BinaryTree binaryTree;
    HashTable hashTable;

    Collection colA;
    colA.set_sort(&merge);
    colA.sort();

    Collection colB;
    colB.set_search(&binaryTree);
    colB.search();

}

 

 

 

 

 

 

 

 

※ Observer Pattern

객체의 변화를 관찰하는 observer들의 목록을 객체에 등록, 변화가 있을 때 함수를 이용해 관찰대상자가 직접 observer에게 통지해 그 객체에 의존성을 가진 다른 객체가자동으로 업데이트 하는 방식

[구조 예시]

● Generator: 관찰 대상자로 현재 관찰 대상자에 붙어있는 Observer들을 관리할뿐만 아니라 현재 관찰 대상자의 상태 정보를 얻기 위한 함수를 제공
           상태 변화시 등록되어 있는 모든 관찰자들에게 상태 변화를 통지해주는 함수를 제공합니다.

● StringGenerator: Generator를 상속받는 실제 상태 정보를 가지고 있는 객체.
                  상태 변화가 발생하면 상태 변화를 통지해주는 함수를 호출.

● Observer: 관찰자들이 가져야 할 공통인터페이스를 정의.

● StringObserver: 관찰 대상자의 상태 정보를 가져와 자신의 상태와 동기화.
                 이 객체는 관찰 대상자의 string형을 모니터에 출력해주는 객체입니다.

● StringCountObsever: 마찬가지로 관찰 대상자의 상태 정보를 가져와 자신의 상태와 동기화 합니다. 
                      이 객체는 관찰 대상자인 string형 문자열의 개수를 화면에 출력하는 객체

 

#include <iostream>
#include <list>
#include <string>
using namespace std;

class IObserver {
public:
    virtual void Update(const string &message_from_subject) = 0;
    virtual ~IObserver(){ };
};

class ISubject {
public:
    virtual void Attach(IObserver *observer) = 0;
    virtual void Detach(IObserver *observer) = 0;
    virtual void Notify() = 0;
    virtual ~ISubject(){};
};

/* Subject는 일부 중요한 state를 소유, state가 변경되면 observer에게 알림*/
class Subject : public ISubject {
public:
    /* subscription 관리 함수 */
    void Attach(IObserver *observer) override { list_observer_.push_back(observer); }
    void Detach(IObserver *observer) override { list_observer_.remove(observer); }
    void Notify() override {
        list<IObserver *>::iterator iterator = list_observer_.begin();
        HowManyObserver();
        while (iterator != list_observer_.end()) {
            (*iterator)->Update(message_);
            ++iterator;
        }
    }
    void CreateMessage(string message = "Empty") {
        this->message_ = message;
        Notify();
    }

    void HowManyObserver() { cout << "There are " << list_observer_.size() << " observers in the list" << endl; }
    
    /*
     * 일반적으로 subscription logic은 Subject가 실제로 수행할 수 있는 작업의 일부이다.
     * Subject는 일반적으로 중요한 일이 발생할 때마다 통지 방법을 작동시키는 중요한 business logic를 갖고 있다.
     */
    void SomeBusinessLogic() {
        this->message_ = "change message message";
        Notify();
        cout << "I'm about to do some thing important\n";
    }

    virtual ~Subject() { cout << "Goodbye, I was the Subject" << endl; }

private:
    list<IObserver *> list_observer_;
    string message_;
};

class Observer : public IObserver {
public:
    Observer(Subject &subject) : subject_(subject) {
        this->subject_.Attach(this); // this는 observer
        cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\"" << endl;
        this->number_ = Observer::static_number_;
    }
    virtual ~Observer() {
        cout << "Goodbye, I was the Observer \"" << this->number_ << "\"" << endl;
    }

    void Update(const string &message_from_subject) override {
        message_from_subject_ = message_from_subject;
        PrintInfo();
    }
    void RemoveMeFromTheList() {
        subject_.Detach(this); // this는 observer
        cout << "Observer \"" << number_ << "\" removed from the list" << endl;
    }
    void PrintInfo() {
        cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << endl;
    }

private:
    std::string message_from_subject_;
    Subject &subject_;
    static int static_number_;
    int number_;
};

int Observer::static_number_ = 0;   // static멤버변수 초기화 방법

void ClientCode() {
    Subject *subject = new Subject;
    Observer *observer1 = new Observer(*subject);
    Observer *observer2 = new Observer(*subject);
    Observer *observer3 = new Observer(*subject);
    Observer *observer4;
    Observer *observer5;

    subject->CreateMessage("Hello World! :D");
    observer3->RemoveMeFromTheList();

    subject->CreateMessage("The weather is hot today! :p");
    observer4 = new Observer(*subject);

    observer2->RemoveMeFromTheList();
    observer5 = new Observer(*subject);

    subject->CreateMessage("My new car is great! ;)");
    observer5->RemoveMeFromTheList();

    observer4->RemoveMeFromTheList();
    observer1->RemoveMeFromTheList();

    delete observer5;
    delete observer4;
    delete observer3;
    delete observer2;
    delete observer1;
    delete subject;
}

int main() {
    ClientCode();
}

 

 

 

 

 

 

 

 

※ Adapter Pattern

변환기처럼 서로 다른 두 인터페이스 사이 통신을 가능하게 해주는 디자인 패턴이다.프로그램에서 한 클래스의 인터페이스를 클라이언트로 사용하고자 하는 인터페이스로 변환 시 사용

또한 어댑터 패턴은 다중상속을 사용해 구현할 수도 있다.

[구조 예시]

 

 

 

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

/* Target: client code에서 사용되는 domain-specific interface를 정의한다. */
class Target {
public:
    virtual std::string Request() const { return "Target: The default target's behavior."; }
    virtual ~Target() = default;
};

/* Adaptee(기존객체)는 유용한 동작이 포함되어 있지만 interface가 기존client code와 호환되지 않는다.
 * 따라서 client code가 Adaptee를 사용하려면 적응할 필요가 있다.
 */
class Adaptee {
public:
    string SpecificRequest() const { return ".eetpadA eht fo roivaheb laicepS"; }
};

/* Adapter는 Adaptee의 interface가 Target's interface와 호환되게 한다. */
class Adapter : public Target {
private:
    Adaptee *adaptee_;
public:
    Adapter() { }
    Adapter(Adaptee *adaptee) : adaptee_(adaptee) {}

    string Request() const override {
        string to_reverse = this->adaptee_->SpecificRequest();
        reverse(to_reverse.begin(), to_reverse.end());
        return "Adapter: (TRANSLATED) " + to_reverse;
    }
};

/* client code는 Target interface를 따르는 모든 클래스를 지원한다. */
void ClientCode(const Target *target) { cout << target->Request(); }

int main() {
    cout << "Client: I can work just fine with the Target objects:\n";

    Target *target = new Target;
    ClientCode(target);
    cout << endl << endl;

    Adaptee *adaptee = new Adaptee;
    cout << "Client: The Adaptee class has a weird interface. See, I don't understand it: " << endl;
    cout << "Adaptee: " << adaptee->SpecificRequest();
    cout << endl << endl;
    cout << "Client: But I can work with it via the Adapter: " << endl;

    Adapter *adapter = new Adapter;
    ClientCode(adapter);
    cout << endl;

    delete target;
    delete adaptee;
    delete adapter;
}

 

 

 

 

 

[다중상속으로 구현한 코드]

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

class Target {
public:
    virtual string Request() const { return "Target: The default target's behavior."; }
    virtual ~Target() = default;
};

class Adaptee {
public:
    string SpecificRequest() const { return ".eetpadA eht fo roivaheb laicepS"; }
};

class Adapter : public Target, public Adaptee {
public:
    Adapter() { }
    string Request() const override {
        string to_reverse = SpecificRequest();
        reverse(to_reverse.begin(), to_reverse.end());
        return "Adapter: (TRANSLATED) " + to_reverse;
    }
};

void ClientCode(const Target *target) { cout << target->Request(); }

int main() {
    cout << "Client: I can work just fine with the Target objects: " << endl;
    
    Target *target = new Target;
    ClientCode(target);
    cout << endl << endl;
    
    Adaptee *adaptee = new Adaptee;
    cout << "Client: The Adaptee class has a weird interface. See, I don't understand it: " << endl;
    cout << "Adaptee: " << adaptee->SpecificRequest();
    cout << endl << endl;
    cout << "Client: But I can work with it via the Adapter: " << endl;
    
    Adapter *adapter = new Adapter;
    ClientCode(adapter);
    cout << endl;

    delete target;
    delete adaptee;
    delete adapter;
}

 

※ Design Pattern이란?

[Buschmann, et al. 1996]_ 최고의 캡슐화의 한 방법으로 매우 효율적으로 프로그래머들이 문제를 해결할 수 있을 것이다.

안정성, 확장성 등에도 효율적이며 패턴이 반복되는 설계문제를 해결하도록 하며 대표적으로 다음과 같은 예시들이 있다.

 

이렇게 많은 디자인 패턴 종류에서 유명한 3가지인 싱글톤, 팩토리 메소드, 브릿지 패턴을 이번에 정리할 것이다.

 

 

※ Singleton

하나의 (전역)인스턴스만 생성하여 사용하는 디자인 패턴.

인스턴스가 필요할 때, 기존의 인스턴스를 활용하는 것.

한번의 new를 통한 객체생성으로 메모리 낭비를 방지가능!

딱 하나의 독자적인 클래스생성을 진행하며 그 클래스의 객체가 복사되면 안된다.

[구조]

class Singleton {
private:
    // 생성자는 private으로 막는다.
    // 따라서 외부에서 new를 이용한 객체생성이 불가능하다.
    Singleton();

    Singleton(const Singleton& ref) { }

    Singleton& operator=(const Singleton& ref) { }

    ~Singleton() { }

    // 객체 하나를 담을 수 있는 포인터 변수를 선언
    // 이때, static으로 선언해서 단 하나만 존재할 수 있게 한다.
    static Singleton *single;

public:
    // single을 가져오거나 해제하는 멤버함수 선언
    // static변수에 접근하고 외부에서 쓸 수 있어야 해서 publc으로 지정
    static Singleton *getInstance();
    static void DestroySingle();
};

Singleton *Singleton::single = nullptr;     // static멤버변수이기에 클래스 외부에서 초기화
Singleton::Singleton() { }

Singleton *Singleton::getInstance() {   
    if (!single)        // single이 nullptr일 때, 
        single = new Singleton;   // 새로 생성해 객체를 초기화
    return single;      // 앞으로 호출될 때마다 이미 생성된 객체를 return
}

void Singleton::DestroySingle() {   // getInstance() 함수와 유사
    if (!single)
        return;
    delete single;
    single = nullptr;
}

 

 

 

 

 

※ Factory Method

객체생성이 복잡하거나 어렵다면, 이를 대행하는 함수를 두는 설계방식.객체를 생성하지만 생성자를 호출하지 않고 대행함수를 통해 간접적으로 객체를 생성한다.

팩토리 메서드 패턴은 복잡도가 낮고 높은 수준의 유연성을 제공해야할 때, 매우 유용하다.

즉, 생성자 기능을 대신하는 메소드를 별도로 정의하는 방식이다.

[구조 예시]

#include <iostream>
#include <string>
using namespace std;


class Product {  // 인터페이스(interface) 선언
public:
    virtual string Operation() const = 0;
    virtual ~Product() { }
};

class ConcreteProduct1 : public Product {   // 인터페이스 상속
public:
    string Operation() const override {
        return "{Result of the ConcreteProduct1}";
    }
};
class ConcreteProduct2 : public Product {   // 인터페이스 상속
public:
    string Operation() const override {
        return "{Result of the ConcreteProduct2}";
    }
};

/* Product 인터페이스의 객체를 return하는 factory method를 선언하는 Creator 클래스
 * Creator클래스의 하위 클래스는 factory method를 상속받음 */

class Creator {     // 인터페이스 클래스는 선언만 진행 (구현X)
public:
    virtual Product *FactoryMethod() const = 0;
    virtual ~Creator(){};

    /* factory method에서 반환된 product 객체에 의존한다. */
    string SomeOperation() const {

        // Product객체생성을 위한 factory method 호출.
        Product *product = this->FactoryMethod();

        string result = "Creator: 동일한 creator의 코드가 " + product->Operation() + "과 작동중";
        delete product;
        return result;
    }
};

/* product type변환을 위해 factory method를 재정의하는  Concrete Creator 클래스들 */
class ConcreteCreator1 : public Creator {
public:
    Product *FactoryMethod() const override {
        return new ConcreteProduct1();
    }
};
class ConcreteCreator2 : public Creator {
public:
    Product *FactoryMethod() const override {
        return new ConcreteProduct2();
    }
};

/* ClientCode 함수는 ConcreteCreator 객체와 함께 작동
 * 기본 인터페이스로 어떤 creator 하위클래스에도 전달 가능 */
void ClientCode(const Creator& creator) {
    cout << "Client: I'm not aware of the creator's class, but it still works.\n" << creator.SomeOperation() << endl;
}

int main() {
    cout << "App: Launched with the ConcreteCreator1.\n";
    Creator *creator = new ConcreteCreator1();
    ClientCode(*creator);
    
    cout << endl;
    
    cout << "App: Launched with the ConcreteCreator2.\n";
    Creator *creator2 = new ConcreteCreator2();
    ClientCode(*creator2);

    delete creator;
    delete creator2;
    return 0;
}

 

 

 

 

 

 

 

 

 

※ Bridge pattern

구현부에서 추상층을 분리, 각각 독립적으로 변형과 확장이 가능하게 하는 패턴  java에선 super키워드도 사용

따라서 두 계층 모두 추상화된 상위 타입을 갖고 의존성은 상위 타입간에만 이뤄진다.

인터페이스와 구현방식이 완전 결합되는 것을 피할 때 사용

하위 클래스 구조가 서로 다른 형태이길 원할 때 사용

 

과거 C++개발자들은 컴파일 단축을 위해 Pimpl이라는 독특한 관례를 사용했다. (Pointer to Implement)

Pimpl은 말그대로 구현부를 포인터로 참조하는 것이다.

장점 1: 클래스의 private, protected멤버가 헤더로 노출되기에 불필요한 노출을 막을 수 있다.

장점 2: 숨겨진 구현클래스에 대한 수정이 바이너리 호환성에 영향X

장점 3: 헤더파일에 구현에 필요한 다른 헤더를 포함하지 않아도 된다.

 

컴포넌트 간 다양한 조합이 가능할 때 효과적이며 실제 구현을 모두 인터페이스에 위임한다.

[구조 예시]

 

 

Abstraction: 기능 계층최상위 클래스.

구현부분에 해당하는 클래스를 객체를 이용구현부분의 함수를 호출

Refind Abstraction: 기능 계층에서 새로운 부분을 확장한 클래스

 

Implementation: 추상클래스의 기능구현하기 위한 인터페이스 정의

Concrete Implementor: 실제 기능구현하는 것

 

#include <iostream>
#include <string>
using namespace std;

/******************************<인터페이스 구현>******************************/
class Implementation {
public:
    virtual string OperationImplementation() const = 0;     // 순수가상함수
    virtual ~Implementation() { }
};
class ConcreteImplementationA : public Implementation {
public:
    string OperationImplementation() const override  {
        return "ConcreteImplementationA: Here's the result on the platform A.\n";
    }
};
class ConcreteImplementationB : public Implementation {
public:
    string OperationImplementation() const override {
        return "ConcreteImplementationB: Here's the result on the platform B.\n";
    }
};

/******************************<추상클래스 구현>******************************/
class Abstraction {
protected:
    // 인터페이스 클래스에 대한 포인터참조로 브릿지 패턴을 잘 나타내고 있음을 알 수 있다.
    Implementation *implementation_;    
public:
    Abstraction(Implementation *implementation) : implementation_(implementation) { }
    virtual string Operation() const {
        return "Abstraction: Base operation with:\n" + this->implementation_->OperationImplementation();
    }
    virtual ~Abstraction() { }
};
class ExtendedAbstraction : public Abstraction {
public:
    ExtendedAbstraction(Implementation* implementation) : Abstraction(implementation) { }
    string Operation() const override {
        return "ExtendedAbstraction: Extended operation with:\n" + this->implementation_->OperationImplementation();
    }
};

void ClientCode(const Abstraction& abstraction) {
    cout << abstraction.Operation();
}

int main() {
    Implementation *implementation = new ConcreteImplementationA;
    Abstraction *abstraction = new Abstraction(implementation);
    ClientCode(*abstraction);

    cout << endl;

    delete implementation;
    delete abstraction;

    implementation = new ConcreteImplementationB;
    abstraction = new ExtendedAbstraction(implementation);
    ClientCode(*abstraction);

    delete implementation;
    delete abstraction;
}

출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=kyung778&logNo=60154874584

 

 

 

+ Recent posts