통보, 열거, 반복문

EHminShoov2J·6일 전
0

Design Pattern

목록 보기
5/6
post-thumbnail

1. Observer pattern

1.1. 의도

  • 객체 사이의 1:N의 종속성을 정의 하고 한 객체의 상태가 변하면 종속된 다른 객체들의 통보가 가고 자동으로 수정이 일어나게 한다.
  • 정보를 보관하는 클래스에서 정보를 필요로 하는 클래스들을 기억하고, 변화가 발생하면 모두에거 notify해준다.
  • Subject에서 기본적인 attach, detach, notify를 구현하고, 이를 상속받는 클래스에서 필요한 데이터 타입만을 관리.

2. Iterator

2.1. 의도

  • 컨테이너의 내부 구조를 노출하지 않고도, 동일한 방법으로 모든 요소에 순차적으로 접근할 수 있게 하자.

2.2. 인터페이스 기반의 설계

  • 모든 Iterator의 사용법이 동일하기 위해서 Iterator의 인터페이스가 필요
  • 반복 가능한 컨테이너 Enumerable은 Iterator를 꺼낼 수 있어야 한다.
template<typename T> struct IEnumerator
{
	virtual T& getObject() = 0;
	virtual bool moveNext() = 0;
	virtual ~IEnumerator() {}
};

template<typename T> struct IEnumerable  
{
	virtual IEnumerator<T>* getEnumerator() = 0;
	virtual ~IEnumerable() {}
};
  • 단점
    • getEnumerator()는 new로 할당하기 때문에 반드시 사용자가 delete 해주어야 하는데, 라이브러리가 할당하고 사람이 delete하는 구조는 좋은 디자인이 아님
    • moveNext(), getObject()등은 가상함수이기 때문에 자주 호출되면 성능 저하를 야기함
    • 모든 컨테이너가 동일한 방식으로 동작하지 않음.(연속된 배열을 가지는 container의 경우 ++을 이용해서 순차 접근)

2.3. STL Style의 Iterator

  • 인터페이스를 사용하지 않고, 인라인 치환 진행
  • 연산자 재정의를 통해 ++, *을 통해서 moveNext(), getObject()등을 대체
  • new 대신 임시객체를 전달하기 때문에 메모리 관리도 수월
template<typename T> class slist 
{
	Node<T>* head = nullptr;
public:
	void push_front(const T& a) { head = new Node<T>(a, head); }

	inline slist_iterator<T> begin()
	{
		return slist_iterator<T>(head); // 임시 객체
	}
	inline  slist_iterator<T> end()
	{
		return slist_iterator<T>(nullptr);
	}
};

3. Visitor

3.1. 의도

  • 객체 구조에 속한 요소에 수행될 오퍼레이션을 정의 하는 객체.
  • Visitor pattern은 처리되어야 하는 요소에 대한 클래스를 변경하지 않고 새로운 오퍼레이션을 정의할 수 있게 한다.

3.2. 예제

  • visitElement() 함수는 방문하고자하는 객체 별로 수행해야할 역할을 정의
  • accept()는 Leaf 노드에서는 전달받은 visitor객체의 visitElement()만을 수행, internal 노드에서는 자신의 childern의 accept()를 호출하도록 구현해야한다.
struct IMenuVisitor
{
	virtual void visit(MenuItem* mi) = 0;
	virtual void visit(PopupMenu* pm) = 0;
	virtual ~IMenuVisitor() {}
};

struct IAcceptor
{
	virtual void accept(IMenuVisitor* visitor) = 0;
	virtual ~IAcceptor() {}
};

class MenuItem : public BaseMenu // leaf node
{
	int id;
public:
	MenuItem(const std::string& title, int id) : BaseMenu(title), id(id) {}

	void accept(IMenuVisitor* visitor)
	{
		visitor->visit(this);
	}

	void command() override {};
};

class PopupMenu : public BaseMenu // internal node
{
	std::vector<BaseMenu*> v;
public:

	PopupMenu(const std::string& title) : BaseMenu(title) {}

	void accept(IMenuVisitor* visitor)
	{
		visitor->visit(this);

		for ( auto child : v)
			//visitor->visit(child);
			child->accept(visitor); // 
	}

	void add_menu(BaseMenu* p) { v.push_back(p); }

	void command() override{}
};

//Visitor
class PopupMenuTitleChangeVisitor : public IMenuVisitor
{
public:
	void visit(MenuItem* mi) {}  // 메뉴아이템 방문시 할일은 없다.

	void visit(PopupMenu* pm) 
	{
		std::string s = "[ " + pm->get_title() + " ]";
		pm->set_title(s);
	}
};

3.3. Visitor의 의미

  • 객체지향의 디자이니 특징 : 새로운 객체 타입을 추가하는 것은 쉬우나 가상함수를 추가하는 것은 어렵다(모든 객체 타입의 수정이 필요).
  • 반대로 Visitor를 사용하면 새로운 객체 타입을 추가하는 것은 어려워지지만, 새로운 오퍼레이션(visitor 작성)을 추가하는 것은 쉽다.
    • 새로운 객체 타입을 추가하면 visitor 인터페이스가 변경됨
    • 각 Visitor 마다 전달되는 객체 타입에 맞는 동작을 구현 필요
    • 결과적으로 기존의 모든 Visitor를 수정해야하는 문제점이 발생.

  • 객체지향의 특징을 반대로 변경해주는 효과를 가진다.

0개의 댓글