On this page
article
Observing and Visiting: Observer, Visitor
Observer
Observer 패턴은 한 객체의 상태 변화가 관련 객체들에게 자동으로 전파되도록 하는 구조이다. 보통 1:N 의존성을 가지며, 한 객체가 바뀌면 연결된 모든 객체가 변경된다.
의도
- 하나의 데이터가 바뀌면 관련된 모든 뷰, 차트 등이 자동으로 업데이트되어야 할 때 사용
- 엑셀에서 셀 하나를 수정하면 종속된 수식이 자동으로 갱신되는 구조와 유사
구조 예시
class IChart {
public:
virtual void update(int data) = 0;
};
class Subject {
std::vector<IChart*> v;
public:
virtual ~Subject() {}
void attach(IChart* p) { v.push_back(p); }
void detach(IChart* p) { /* 구현 생략 */ }
void notify(int data) {
for (auto p : v)
p->update(data);
}
};
class Table : public Subject {
int data;
public:
void edit() {
while (1) {
std::cout << "data >> ";
std::cin >> data;
notify(data);
}
}
};
Visitor
Visitor 패턴은 객체 구조는 그대로 두고, 새로운 기능을 외부에서 추가할 수 있도록 돕는 패턴이다. 방문자 객체가 구조 내부의 요소를 순회하며 적절한 처리를 수행한다.
예시: 문서 요소 렌더링
// 방문 대상 요소 인터페이스
struct DocumentElement {
virtual void accept(class DocumentVisitor* visitor) = 0;
virtual ~DocumentElement() {}
};
// 구체 요소 1: 텍스트
class TextElement : public DocumentElement {
std::string text;
public:
TextElement(std::string t) : text(t) {}
void accept(DocumentVisitor* visitor) override;
std::string getText() const { return text; }
};
// 구체 요소 2: 이미지
class ImageElement : public DocumentElement {
std::string path;
public:
ImageElement(std::string p) : path(p) {}
void accept(DocumentVisitor* visitor) override;
std::string getPath() const { return path; }
};
// 방문자 인터페이스
struct DocumentVisitor {
virtual void visit(TextElement* e) = 0;
virtual void visit(ImageElement* e) = 0;
};
// 방문자 구현
class HtmlRenderer : public DocumentVisitor {
public:
void visit(TextElement* e) override {
std::cout << "<p>" << e->getText() << "</p>" << std::endl;
}
void visit(ImageElement* e) override {
std::cout << "<img src='" << e->getPath() << "' />" << std::endl;
}
};
// accept 구현
void TextElement::accept(DocumentVisitor* visitor) { visitor->visit(this); }
void ImageElement::accept(DocumentVisitor* visitor) { visitor->visit(this); }
// 사용 예시
int main() {
std::vector<DocumentElement*> doc = {
new TextElement("Hello, Visitor!"),
new ImageElement("logo.png")
};
HtmlRenderer renderer;
for (auto e : doc) e->accept(&renderer);
for (auto e : doc) delete e;
return 0;
}
```cpp
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() {}
};
// PopupMenu 내부의 구현
void accept(IMenuVisitor* visitor) override {
visitor->visit(this);
for (auto child : v)
child->accept(visitor);
}
장점
- 객체 구조는 그대로 유지하면서 연산을 추가할 수 있다
- 여러 다른 연산을 별도의 Visitor로 분리 가능
핵심 비교 요약
패턴 | 역할 및 목적 |
---|---|
Observer | 상태 변경이 자동으로 연결 객체에 전파됨 |
Visitor | 요소에 대한 연산을 객체 외부에서 정의할 수 있음 |
이처럼 Observer는 상태 알림, Visitor는 연산 분리에 집중하며, 둘 다 구조 유지를 하면서 유연성을 높이는 데 도움을 준다.