본문 바로가기
Thinking/Concept

방문자 패턴(Visitor Pattern) 이해하기

by Dev_카페인 2024. 11. 17.
반응형

방문자 패턴(Visitor Pattern) 이해하기

방문자 패턴객체의 구조와 객체가 수행할 작업을 분리하여, 객체 구조를 변경하지 않고도 새로운 작업(동작)을 추가할 수 있도록 설계된 행동 디자인 패턴입니다. 이 패턴은 객체의 내부 구조를 노출하지 않고, 객체에 수행할 작업을 외부에서 정의할 수 있도록 합니다.

방문자 패턴은 특히 여러 객체가 공통적인 인터페이스를 공유하고, 객체의 종류에 따라 다르게 처리해야 하는 경우에 유용합니다. 이번 포스트에서는 방문자 패턴의 개념, 구조, 구현 방법, 장단점 및 사용 사례를 구체적인 예제와 함께 살펴보겠습니다.

 

방문자 패턴이란?

방문자 패턴은 객체 구조(Element)와, 이 객체들에 수행할 작업(Visitor)을 분리하는 디자인 패턴입니다. 객체 구조는 변경하지 않으면서, 객체에 새로운 작업(행동)을 추가할 수 있습니다.

주요 개념은 객체 구조를 유지하면서, 새로운 동작을 추가할 때 기존 코드를 수정하지 않고 방문자(Visitor) 클래스에 작업을 정의하는 것입니다.

방문자 패턴의 구조

방문자 패턴은 다음과 같은 구성 요소로 이루어집니다.

  1. Visitor (방문자 인터페이스):
    • 객체 구조의 각 요소(Element)를 방문할 메서드를 정의합니다.
  2. ConcreteVisitor (구체적인 방문자):
    • Visitor 인터페이스를 구현하며, 요소(Element)에 대해 수행할 작업을 정의합니다.
  3. Element (요소 인터페이스):
    • Accept 메서드를 정의하여, 방문자(Visitor)를 수락할 수 있도록 합니다.
  4. ConcreteElement (구체적인 요소):
    • Element 인터페이스를 구현하며, 자신의 데이터와 작업을 방문자에게 제공합니다.
  5. ObjectStructure (객체 구조):
    • 요소 객체들의 집합을 관리하며, 각 요소가 방문자를 수락하도록 요청합니다.

방문자 패턴의 구현 방법

예제 코드: 파일 시스템 구조와 작업

방문자 패턴을 활용하여 파일 시스템에서 파일과 폴더에 대한 작업(크기 계산, 파일 리스트 출력)을 구현해보겠습니다.

// Visitor 인터페이스
public interface IVisitor
{
    void Visit(File file);
    void Visit(Folder folder);
}

// ConcreteVisitor 클래스: 크기 계산
public class SizeCalculator : IVisitor
{
    private int _totalSize = 0;

    public void Visit(File file)
    {
        _totalSize += file.Size;
    }

    public void Visit(Folder folder)
    {
        // 폴더는 별도의 크기를 가지지 않음
    }

    public int GetTotalSize()
    {
        return _totalSize;
    }
}

// ConcreteVisitor 클래스: 파일 리스트 출력
public class FileLister : IVisitor
{
    public void Visit(File file)
    {
        Console.WriteLine($"File: {file.Name}");
    }

    public void Visit(Folder folder)
    {
        Console.WriteLine($"Folder: {folder.Name}");
    }
}

// Element 인터페이스
public interface IFileSystemElement
{
    void Accept(IVisitor visitor);
}

// ConcreteElement 클래스: 파일
public class File : IFileSystemElement
{
    public string Name { get; }
    public int Size { get; }

    public File(string name, int size)
    {
        Name = name;
        Size = size;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// ConcreteElement 클래스: 폴더
public class Folder : IFileSystemElement
{
    public string Name { get; }
    public List<IFileSystemElement> Elements { get; } = new List<IFileSystemElement>();

    public Folder(string name)
    {
        Name = name;
    }

    public void AddElement(IFileSystemElement element)
    {
        Elements.Add(element);
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
        foreach (var element in Elements)
        {
            element.Accept(visitor);
        }
    }
}

// Client 코드
class Program
{
    static void Main()
    {
        // 파일 시스템 구성
        File file1 = new File("File1.txt", 100);
        File file2 = new File("File2.txt", 200);
        Folder folder = new Folder("Documents");
        folder.AddElement(file1);
        folder.AddElement(file2);

        // 크기 계산
        SizeCalculator sizeCalculator = new SizeCalculator();
        folder.Accept(sizeCalculator);
        Console.WriteLine($"Total Size: {sizeCalculator.GetTotalSize()}");

        // 파일 리스트 출력
        FileLister fileLister = new FileLister();
        folder.Accept(fileLister);
    }
}

실행 결과

Total Size: 300
Folder: Documents
File: File1.txt
File: File2.txt
 

 

코드 설명

  1. Visitor:
    • IVisitor 인터페이스는 파일과 폴더를 방문할 메서드를 정의합니다(Visit(File file)와 Visit(Folder folder)).
  2. ConcreteVisitor:
    • SizeCalculator는 파일의 크기를 계산하는 작업을, FileLister는 파일과 폴더의 이름을 출력하는 작업을 구현합니다.
  3. Element:
    • IFileSystemElement 인터페이스는 Accept(IVisitor visitor) 메서드를 정의하여 방문자를 수락할 수 있도록 합니다.
  4. ConcreteElement:
    • File 클래스는 파일의 이름과 크기를 제공하며, Folder 클래스는 하위 요소들을 관리하고 방문자를 재귀적으로 호출합니다.
  5. Client:
    • 클라이언트는 방문자를 생성하고, 객체 구조의 요소들이 방문자를 수락하도록 요청하여 작업을 수행합니다.

 

방문자 패턴의 장단점

장점

  1. 새로운 작업의 추가가 용이:
    • 객체 구조를 변경하지 않고도 새로운 작업(Visitor)을 쉽게 추가할 수 있습니다.
  2. 단일 책임 원칙 준수:
    • 객체 구조(Element)와 작업(Visitor)을 분리하여, 각자의 책임을 명확히 분리합니다.
  3. 객체 구조의 일관성 유지:
    • 객체 구조를 변경하지 않고도 다양한 동작을 추가할 수 있습니다.

단점

  1. 객체 구조 변경의 어려움:
    • 객체 구조(Element)가 변경되면, 모든 Visitor 클래스가 영향을 받습니다.
  2. 복잡성 증가:
    • 각 요소(Element)에 대한 방문 메서드를 Visitor 인터페이스에 추가해야 하므로, 요소가 많아질수록 코드가 복잡해질 수 있습니다.
  3. 캡슐화 위반 가능성:
    • Visitor가 객체의 내부 상태에 접근해야 하는 경우, 캡슐화 원칙이 위반될 수 있습니다.

 

언제 방문자 패턴을 사용해야 할까?

방문자 패턴은 다음과 같은 상황에서 유용합니다.

  1. 객체 구조는 변경되지 않으면서 새로운 작업을 추가해야 할 때:
    • 객체 구조를 자주 변경하지 않고, 새로운 동작만 추가해야 할 때.
  2. 다양한 객체에 공통된 작업을 수행해야 할 때:
    • 서로 다른 타입의 객체에 대해 동일하거나 유사한 작업을 수행해야 할 때.
  3. 객체 구조와 작업 로직을 분리해야 할 때:
    • 객체 구조(Element)와 작업(Visitor)을 독립적으로 유지해야 할 때.

 

방문자 패턴과 관련 패턴 비교

방문자 패턴은 객체 구조와 작업을 분리하여 동작을 추가할 수 있습니다. 다른 디자인 패턴과의 차이를 살펴보겠습니다.

  • 템플릿 메서드 패턴:
    • 알고리즘의 골격을 정의하고, 일부 세부 동작을 하위 클래스에서 구현하도록 합니다. 방문자 패턴은 객체 구조에 대해 외부에서 동작을 추가합니다.
  • 컴포지트 패턴:
    • 객체를 트리 구조로 구성하여 계층적으로 처리합니다. 방문자 패턴은 객체 구조를 유지하면서, 외부에서 작업을 정의합니다.
  • 전략 패턴:
    • 동작을 캡슐화하고, 런타임에 교체할 수 있도록 합니다. 방문자 패턴은 객체 구조와 무관하게 작업을 추가합니다.

 

실무에서 방문자 패턴 활용하기

방문자 패턴은 다음과 같은 실무 상황에서 활용됩니다.

  1. 파일 시스템:
    • 파일과 폴더의 크기 계산, 파일 리스트 출력, 검색 등의 작업 구현.
  2. 컴파일러:
    • 구문 트리(AST, Abstract Syntax Tree)에서 코드 분석, 최적화, 코드 생성 등의 작업.
  3. 그래픽 엔진:
    • 도형(점, 선, 사각형 등)에 대해 렌더링, 충돌 처리, 변환 작업.
  4. 데이터 분석:
    • 다양한 데이터 구조(예: JSON, XML)에 대해 데이터 변환, 검증, 추출 작업.

 

마무리하며

방문자 패턴은 객체 구조와 작업을 분리하여, 새로운 작업을 유연하게 추가할 수 있는 강력한 패턴입니다. 특히, 객체 구조가 복잡하거나 자주 변경되지 않는 시스템에서 큰 효과를 발휘합니다.

객체 구조의 변경 없이 새로운 작업을 추가해야 하는 시스템을 설계할 때, 방문자 패턴을 활용해보세요. 이를 통해 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다.

 

반응형