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는 연산 분리에 집중하며, 둘 다 구조 유지를 하면서 유연성을 높이는 데 도움을 준다.