2학년 디자인패턴 Behavioral Patterns

Chain of Responsibility(책임 연쇄 패턴)

  • 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴

  • 예시

  • UML

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

class Handler {
public:
	virtual Handler* SetNext(Handler* handler) = 0;
	virtual string Handle(string request) = 0;
};

class AbstractHandler : public Handler {
private:
	Handler* next_handler_;
public:
	AbstractHandler() : next_handler_(nullptr) { }
	Handler* SetNext(Handler* handler) {
		this->next_handler_ = handler;
		return handler;
	}
	string Handle(string request) {
		if (this->next_handler_) { return this->next_handler_->Handle(request); }
		return {};
	}
};

class AHandler : public AbstractHandler {
public:
	string Handle(string request) override {
		if (request == "A") { return "Request A Done"; }
		else { return AbstractHandler::Handle(request); }
	}
};

class BHandler : public AbstractHandler {
public:
	string Handle(string request) override {
		if (request == "B") { return "Request B Done"; }
		else { return AbstractHandler::Handle(request); }
	}
};

int main() {
	AHandler* a = new AHandler;
	BHandler* b = new BHandler;
	a->SetNext(b);
	vector<string> requests = { "B", "C", "A" };
	for (const string& f : requests) {
		const string result = a->Handle(f);
		if (!result.empty()) {
			cout << result << endl;
		}
		else {
			cout << "Request " << f << " left untouched." << endl;
		}
	}
	return 0;
}
  • 장점

    • 요청 처리 순서를 제어할 수 있다
    • 단일 책임원칙, 개발/폐쇄 원칙
  • 단점

    • 일부 요청은 처리되지 않을 수 있다

Command

  • 요청을 객체의 형태로 갭슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 메서드 이름, 매개변수, 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다

  • 예시

  • UML

  • invoker = button, Receiver = product

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

class Receiver {
public:
	void execute(const string& a) {
		cout << a << " done" << endl;
	}
};

class Command {
public:
	virtual void Execute() const = 0;
};

class Command1 : public Command {
private:
	Receiver* receiver_;
	string a_;
public:
	Command1(Receiver* receiver, string a) : receiver_(receiver), a_(a) { }
	void Execute() const override { this->receiver_->execute(this->a_); }
};

class Command2 : public Command {
private:
	Receiver* receiver_;
	string a_;
	string b_;
public:
	Command2(Receiver* receiver, string a, string b) : receiver_(receiver), a_(a), b_(b) { }
	void Execute() const override {
		this->receiver_->execute(this->a_);
		this->receiver_->execute(this->b_);
	}
};

class Invoker {
private:
	Command* c_;
public:
	void setCommand(Command* command) {
		this->c_ = command;
	}
	void Do() {
		this->c_->Execute();
	}
};

int main() {
	Invoker* invoker = new Invoker;
	Receiver* receiver = new Receiver;
	invoker->setCommand(new Command1(receiver, "Americano"));
	invoker->Do();
	invoker->setCommand(new Command2(receiver, "Americano", "Mocha"));
	invoker->Do();
	return 0;
}
  • 장점 : 실행취소, 다시실행, 지연된 작업실행 구현 가능

Iterator

  • 반복자를 사용하여 컨테이너를 가로지르며 컨테이너의 요소들에 접근하는 디자인패턴

  • 예시, UML

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

class Data
{
public:
	Data(int
		a = 0) : m_data_(
			a) {}
	void setData
	(int
		a) {
		m_data_ =
			a;
	}
	int getData() {
		return m_data_;
	}
private:
	int m_data_;
};

typedef typename std::vector<Data>::iterator iter_type;
class Iterator {
public:
	Iterator(vector<Data>* p_data) : m_p_data_(p_data) { First(); }
	void First() { m_it_ = m_p_data_->begin(); }
	void Next() { m_it_++; }
	bool IsDone() { return (m_it_ == m_p_data_->end()); }
	iter_type Current() { return m_it_; }
private:
	vector<Data>* m_p_data_;
	iter_type m_it_;
};

int main() {
	vector<Data> cont;
	Data a(100), b(1000), c(10000);
	cont.push_back(a);
	cont.push_back(b);
	cont.push_back(c);
	Iterator* it = new Iterator(&cont);
	for (it->First(); !it->IsDone(); it->Next()) {
		std::cout << it->Current()->getData() << std::endl;
	}
	return 0;
}

Mediator

  • 중재자는 개체가 서로를 명시적으로 참조하지 않도록하여 느슨한 결합을 촉진합니다

  • 예시

  • UML

  • Colleague1, Colleague2는 소통을 하려고 하지 않는다

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

class Mediator {
public:
	virtual void Notify(BaseComponent* sender, string event) const = 0;
};

class BaseComponent {
protected:
	Mediator* mediator_;
public:
	BaseComponent(Mediator* mediator = nullptr) : mediator_(mediator) {
	}
	void set_mediator(Mediator* mediator) {
		this->mediator_ = mediator;
	}
};

class ConcreteMediator : public Mediator {
private:
	Component1* component1_;
	Component2* component2_;
public:
	ConcreteMediator(Component1* c1, Component2* c2) : component1_(c1), component2_(c2) {
		this->component1_->set_mediator(this);
		this->component2_->set_mediator(this);
	}
	void Notify(BaseComponent* sender, std::string event) const override {
		if (event == "A") { this->component1_->Msg(); }
		if (event == "B") { this->component2_->Msg(); }
	}
};

class Component1 : public BaseComponent {
public:
	void DoA() { this->mediator_->Notify(this, "A"); }
	void Msg() { cout << "Want to grab lunch?" << endl; }
};

class Component2 : public BaseComponent {
public:
	void DoB() { this->mediator_->Notify(this, "B"); }
	void Msg() { cout << "No" << endl; }
};

int main() {
	Component1* c1 = new Component1;
	Component2* c2 = new Component2;
	ConcreteMediator* mediator = new ConcreteMediator(c1, c2);
	c1->DoA();
	c2->DoB();
	delete c1;
	delete c2;
	delete mediator;
	return 0;
}
  • 장점
    • 프로그램의 다양한 구성 요소간의 결합을 줄일 수 있다
    • 개별 컴포넌트를 보다 쉽게 재사용할 수 있다
  • 단점
    • 시간이 지남에 따라 mediator는 신의 대상으로 될 수 있다

Memento

  • 객체를 이전상태로 되돌릴수 있는 기능을 제공 -> version control system(git)

  • 예시

  • UML

  • originator -> 수정

  • memento -> state

  • caretaker -> memento manager

#include<iostream>
#include <list> 
#include<string>
#include<vector>
#include<cstdlib>
#include<ctime>

using namespace std;

class Memento {
public:
	virtual int getState() const = 0;
};

class ConcreteMemento : public Memento {
private:
	int state_;
public:
	ConcreteMemento(int state) : state_(state) {
		this->state_ = state;
	}
	int getState() const {
		return this->state_;
	}
};

class Originator {
private:
	int state_;
public:
	Originator(int state) : state_(state) {
		cout << "Current State: " << this->state_ << endl;
	}
	void DoSomething() {
		this->state_ += rand() % 10;
		cout << "State has been changed to: " << this->state_ << endl;
	}
	Memento* Save() {
		return new ConcreteMemento(this->state_);
	}
	void Restore(Memento* memento) {
		this->state_ = memento->getState();
		cout << "State has been changed to: " << this->state_ << endl;
	}
};

class Caretaker {
private:
	std::vector	<Memento*> mementos_;
	Originator* originator_;
public:
	Caretaker(Originator* originator) : originator_(originator) {
		this->originator_ = originator;
	}
	void Backup() {
		cout << "Saving state...\n";
		this->mementos_.push_back(this->originator_->Save());
	}
	void Undo() {
		if (!this->mementos_.size()) {
			return;
		}
		Memento* memento = this->mementos_.back();
		this->mementos_.pop_back();
		std::cout << "Restoring state to: " << memento->getState() << endl;
		try {
			this->originator_->Restore(memento);
		}
		catch (...) {
			this->Undo();
		}
	}
};

int main() {
	srand(static_cast<unsigned int>(std::time(NULL)));
	Originator* originator = new Originator(0);
	Caretaker* caretaker = new Caretaker(originator);
	caretaker->Backup();
	originator->DoSomething();
	caretaker->Backup();
	originator->DoSomething();
	caretaker->Undo();
	caretaker->Undo();
	return 0;
}
  • 장점
    • 객체상태의 스냅샷을 생성할 수 있다
  • 단점
    • memento를 자주 생성하면 RAM을 많이 소모할 수 있다
    • 수명 주기를 추적해야 한다

Observer

  • 객체의 상태변화가 있을 때마다 메서드, 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하는 패턴

  • 예시

  • UML

#include<iostream>
#include <list> 
#include<string>
#include<vector>
#include<cstdlib>
#include<ctime>
using namespace std;

class IObserver {
public:
	virtual void Update(const string& message_from_subject) = 0;
};
class Observer : public IObserver {
public:
	Observer(Subject& subject) : subject_(subject) {
		this->subject_.Attach(this);
	}
	void Update(const std::string& message_from_subject) override {
		message_from_subject_ = message_from_subject;
		PrintInfo();
	}
	void PrintInfo() {
		cout << "Observer: New message arrived --> [ " << this->message_from_subject_ << " ]" << endl;
	}
private:
	std::string message_from_subject_;
	Subject& subject_;
};

class ISubject {
public:
	virtual void Attach(IObserver* observer) = 0;
	virtual void Notify() = 0;
};
class Subject : public ISubject {
public:
	void Attach(IObserver* observer) { list_observer_.push_back(observer); }
	void Notify() {
		std::list<IObserver*>::iterator iterator = list_observer_.begin();
		while (iterator != list_observer_.end()) {
			(*iterator)->Update(message_);
			++iterator;
		}
	}
	void CreateMessage(string message) {
		this->message_ = message;
		Notify();
	}
private:
	list<IObserver*> list_observer_;
	string message_;
};

int main() {
	Subject* subject = new Subject;
	Observer* observer1 = new Observer(*subject);
	Observer* observer2 = new Observer(*subject);
	Observer* observer3 = new Observer(*subject);
	subject->CreateMessage("New Drink Available");
	return 0;
}

장점 : 객체 간의 관계를 런타임에 설정할 수 있다
단점 : 무작위로 통보된다

State

  • 객체의 내부 상태가 변경될 때 객체가 동작을 변경하도록 허용합니다. 개체는 클래스를 변경하는 것으로 나타난다

  • 예시

  • UML

#include<iostream>
#include <list> 
#include<string>
#include<vector>
#include<cstdlib>
#include<ctime>
using namespace std;

class Context;
class State {
protected:
	Context* context_;
public:
	void set_context(Context* context) {
		this->context_ = context;
	}
	virtual void Handle1() = 0;
};

class Context{
private:
	State* state_;
public:
	Context(State* state) : state_(nullptr) {
		this-> TransitionTo(state);
	}
	~Context() { delete state_; }
	void TransitionTo(State* state) {
		if(this->state_ != nullptr)
			delete this->state_;
		this->state_ = state;
		this->state_->set_context(this);
	}
	void Request1() {
		this->state_->Handle1();
	}
};

class ReadyState : public State {
public: void Handle1();
};
class RunState : public State {
public: void Handle1();
};
class OffState : public State {
public: void Handle1();
};

void ReadyState::Handle1() {
	cout << "[Ready State] --> [Run State]" << endl;
	this->context_->TransitionTo(new RunState);
};
void RunState::Handle1() {
	cout << "[Run State] --> [Off State]" << endl;
	this->context_->TransitionTo(new OffState);
};
void OffState::Handle1() {
	cout << "[Off State] --> [Ready State]" << endl;
	this->context_->TransitionTo(new ReadyState);
};

int main() {
	Context* context = new Context(new OffState);
	context->Request1();
	context->Request1();
	context->Request1();
	return 0;
}
  • 장점 : 큰 상태 머신 조건을 제거하여 컨텍스트의 코드를 단순화합니다.
  • 단점 : 상태 머신에 몇 가지 상태만 있거나 거의 변경되지 않는 경우 패턴을 적용하는 것이 과도할 수 있습니다.

Strategy

  • 특정한 계열의 알고리즘들을 정의하고 각 알고리즘을 캡슐화하여 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다

  • 예시

  • UML

#include<iostream>
#include <list> 
#include<string>
#include<vector>
#include<cstdlib>
#include<ctime>
using namespace std;

class Strategy {
public:
	virtual void doAlgorithm() const = 0;
};
class ConcreteStrategyA : public Strategy {
public:
	void doAlgorithm() const {
		cout << "Algorithm A is applied" << endl;
	}
};
class ConcreteStrategyB : public Strategy {
	void doAlgorithm() const {
		cout << "Algorithm B is applied" << endl;
	}
};

class Context
{
private:
	Strategy* strategy_;
public:
	Context(Strategy* strategy = nullptr) : strategy_(strategy) { }
	void set_strategy(Strategy* strategy)
	{
		delete this->strategy_;
		this->strategy_ = strategy;
	}
	void makeCoffee() const
	{
		this->strategy_->doAlgorithm();
	}
};

int main()
{
	Context* context = new Context(new ConcreteStrategyA);
	context->makeCoffee();
	context->set_strategy(new ConcreteStrategyB);
	context->makeCoffee();
	return 0;
}

state pattern이랑 유사

  • 장점

    • 런타임에 객체 내부에서 사용되는 알고리즘을 교환할 수 있습니다.
    • 알고리즘을 사용하는 코드에서 알고리즘의 구현 세부 정보를 분리할 수 있습니다.
  • 단점

    • 알고리즘이 몇 개만 있고 거의 변경되지 않는다면 새 클래스와 인터페이스로 프로그램을 지나치게 복잡하게 만들 이유가 없습니다.

Template Method

  • 작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다.

  • 예시

  • UML

Template Method -> factory method, abstract factory의 근본이 된다

class AbstractClass
{
public:
	void TemplateMethod() {
		this
			->BaseOperation1();
		this
			->RequiredOperation1();
		this
			->BaseOperation2();
		this
			->RequiredOperation2();
	}
protected:
	void BaseOperation1() const
	{
		cout << "Base Operation 1" << endl;
	}
	void BaseOperation2() const
	{
		cout << "Base Operation 2" << endl;
	}
	virtual void RequiredOperation1() const = 0;
	virtual void RequiredOperation2() const = 0;
};

장점 : 클라이언트가 큰 알고리즘의 특정 부분만 재정의하도록 하여 알고리즘의 다른 부분에서 발생하는 변경 사항의 영향을 덜 받도록 할 수 있습니다.
단점 : 일부 클라이언트는 제공된 알고리즘의 골격에 의해 제한될 수 있습니다.

visitor

  • 알고리즘을 객체 구조에서 분리시키는 디자인 패턴이다.

  • 클래스에서 data영역과 function영역을 나눈다

  • 예시

  • UML

#include<iostream>
#include <list> 
#include<string>
#include<vector>
#include<cstdlib>
#include<ctime>
using namespace std;

class Component{
public:
	virtual void Accept(Visitor* visitor) = 0;
};

class Americano : public Component{
public:
	void Accept(Visitor* visitor) {
		visitor	-> VisitConcreteComponentA(this);
	}
};
class Latte : public Component{
public:
	void Accept(Visitor* visitor) {
		visitor	-> VisitConcreteComponentB(this);
	}
};
class Visitor {
public:
	virtual void VisitConcreteComponentA(const Americano* element) = 0;
	virtual void VisitConcreteComponentB(const Latte* element) = 0;
};
class ConcreteVisitor1 : public Visitor {
public:
	void VisitConcreteComponentA(const Americano* element) {
		cout << "Americano" << endl;
	}
	void VisitConcreteComponentB(const Latte* element) {
		cout << "Latte" << endl;
	}
};
int main() {
	Component* arr[2] = { new Americano, new Latte };
	ConcreteVisitor1* visitor1 = new ConcreteVisitor1;
	for (int i = 0; i < 2; i++) {
		arr[i]->Accept(visitor1);
	}
	return 0;
}

단점 : 방문자는 개인 필드 및 작업해야 하는 요소의 메서드에 대한 필수 액세스 권한이 부족할 수 있다.

좋은 웹페이지 즐겨찾기