본문 바로가기
CS(Computer Science)지식/[C++] 디자인 패턴

방문자 패턴(Visitor Pattern)이해하기 : 쉽게 설명한 디자인 패턴(C++)

by 엔지니어 청년 2024. 1. 29.

방문자 패턴(Visitor Pattern)이란?

방문자 패턴은 객체 지향 디자인 패턴 중 하나로, 특정 연산을 객체의 구조에 대해 수행하도록 하는 패턴입니다. 이 패턴은 연산을 수행하려는 객체에서 분리하여 새로운 연산을 추가하거나 기존 연산을 변경하는 것을 쉽게 만들어줍니다. 이 패턴은 주로 복잡한 객체 구조에 대해 연산을 수행할 때 사용됩니다.

방문자 패턴은 다음 두 가지 주요 구성 요소로 이루어져 있습니다:

  1. Visitor: 방문자 인터페이스로, 방문할 객체의 클래스에 대한 방문 연산을 선언합니다.
  2. ConcreteVisitor: 방문자 인터페이스를 구현하고, 각 클래스에 대한 방문 연산을 정의합니다.

예시 코드

다음은 C++로 작성된 간단한 방문자 패턴의 예입니다:

#include <iostream>

class ConcreteElementA;
class ConcreteElementB;

class Visitor {
public:
    virtual void visitConcreteElementA(ConcreteElementA* element) = 0;
    virtual void visitConcreteElementB(ConcreteElementB* element) = 0;
};

class ConcreteVisitor1 : public Visitor {
public:
    void visitConcreteElementA(ConcreteElementA* element) override {
        std::cout << "ConcreteVisitor1: Visited ConcreteElementA\n";
    }
    void visitConcreteElementB(ConcreteElementB* element) override {
        std::cout << "ConcreteVisitor1: Visited ConcreteElementB\n";
    }
};

class ConcreteVisitor2 : public Visitor {
public:
    void visitConcreteElementA(ConcreteElementA* element) override {
        std::cout << "ConcreteVisitor2: Visited ConcreteElementA\n";
    }
    void visitConcreteElementB(ConcreteElementB* element) override {
        std::cout << "ConcreteVisitor2: Visited ConcreteElementB\n";
    }
};

class Element {
public:
    virtual void accept(Visitor* visitor) = 0;
};

class ConcreteElementA : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visitConcreteElementA(this);
    }
};

class ConcreteElementB : public Element {
public:
    void accept(Visitor* visitor) override {
        visitor->visitConcreteElementB(this);
    }
};

int main() {
    ConcreteElementA elementA;
    ConcreteElementB elementB;
    ConcreteVisitor1 visitor1;
    ConcreteVisitor2 visitor2;

    elementA.accept(&visitor1);
    elementB.accept(&visitor1);
    elementA.accept(&visitor2);
    elementB.accept(&visitor2);
}

위 코드에서 Visitor는 방문자 인터페이스로, 방문할 객체의 클래스에 대한 방문 연산을 선언합니다. ConcreteVisitor1ConcreteVisitor2Visitor 인터페이스를 구현하고, 각 클래스에 대한 방문 연산을 정의합니다. Element는 방문할 객체의 인터페이스로, 방문자를 받아들이는 연산을 선언합니다. ConcreteElementAConcreteElementBElement 인터페이스를 구현하고, 방문자를 받아들이는 연산을 정의합니다.

클래스 다이어그램

    +-----------------+
    |     Visitor     |
    +-----------------+
    | visitConcreteElementA() |
    | visitConcreteElementB() |
    +-----------------+
            ^
            |
    +-----------------+     +-----------------+
    | ConcreteVisitor1|     | ConcreteVisitor2|
    +-----------------+     +-----------------+
    | visitConcreteElementA() |     | visitConcreteElementA() |
    | visitConcreteElementB() |     | visitConcreteElementB() |
    +-----------------+     +-----------------+

    +-----------------+
    |     Element     |
    +-----------------+
    | accept()        |
    +-----------------+
            ^
            |
    +-----------------+     +-----------------+
    | ConcreteElementA|     | ConcreteElementB|
    +-----------------+     +-----------------+
    | accept()        |     | accept()        |
    +-----------------+     +-----------------+

방문자 패턴은 객체 구조와 연산을 분리하여 코드의 유연성을 높이고, 새로운 연산을 쉽게 추가할 수 있게 해줍니다. 하지만 이 패턴을 사용하면 코드가 복잡해질 수 있으므로, 실제로 객체 구조에 대해 복잡한 연산을 수행해야 하는 경우에만 이 패턴을 사용하는 것이 좋습니다.