1.1. 의도
- 객체 사이의 1:N의 종속성을 정의 하고 한 객체의 상태가 변하면 종속된 다른 객체들의 통보가 가고 자동으로 수정이 일어나게 한다.
- 정보를 보관하는 클래스에서 정보를 필요로 하는 클래스들을 기억하고, 변화가 발생하면 모두에거 notify해준다.
- Subject에서 기본적인 attach, detach, notify를 구현하고, 이를 상속받는 클래스에서 필요한 데이터 타입만을 관리.
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.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를 수정해야하는 문제점이 발생.
- 객체지향의 특징을 반대로 변경해주는 효과를 가진다.