방문자 패턴(Visitor Pattern) 이해하기
방문자 패턴은 객체의 구조와 객체가 수행할 작업을 분리하여, 객체 구조를 변경하지 않고도 새로운 작업(동작)을 추가할 수 있도록 설계된 행동 디자인 패턴입니다. 이 패턴은 객체의 내부 구조를 노출하지 않고, 객체에 수행할 작업을 외부에서 정의할 수 있도록 합니다.
방문자 패턴은 특히 여러 객체가 공통적인 인터페이스를 공유하고, 객체의 종류에 따라 다르게 처리해야 하는 경우에 유용합니다. 이번 포스트에서는 방문자 패턴의 개념, 구조, 구현 방법, 장단점 및 사용 사례를 구체적인 예제와 함께 살펴보겠습니다.
방문자 패턴이란?
방문자 패턴은 객체 구조(Element)와, 이 객체들에 수행할 작업(Visitor)을 분리하는 디자인 패턴입니다. 객체 구조는 변경하지 않으면서, 객체에 새로운 작업(행동)을 추가할 수 있습니다.
주요 개념은 객체 구조를 유지하면서, 새로운 동작을 추가할 때 기존 코드를 수정하지 않고 방문자(Visitor) 클래스에 작업을 정의하는 것입니다.
방문자 패턴의 구조
방문자 패턴은 다음과 같은 구성 요소로 이루어집니다.
- Visitor (방문자 인터페이스):
- 객체 구조의 각 요소(Element)를 방문할 메서드를 정의합니다.
- ConcreteVisitor (구체적인 방문자):
- Visitor 인터페이스를 구현하며, 요소(Element)에 대해 수행할 작업을 정의합니다.
- Element (요소 인터페이스):
- Accept 메서드를 정의하여, 방문자(Visitor)를 수락할 수 있도록 합니다.
- ConcreteElement (구체적인 요소):
- Element 인터페이스를 구현하며, 자신의 데이터와 작업을 방문자에게 제공합니다.
- 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
코드 설명
- Visitor:
- IVisitor 인터페이스는 파일과 폴더를 방문할 메서드를 정의합니다(Visit(File file)와 Visit(Folder folder)).
- ConcreteVisitor:
- SizeCalculator는 파일의 크기를 계산하는 작업을, FileLister는 파일과 폴더의 이름을 출력하는 작업을 구현합니다.
- Element:
- IFileSystemElement 인터페이스는 Accept(IVisitor visitor) 메서드를 정의하여 방문자를 수락할 수 있도록 합니다.
- ConcreteElement:
- File 클래스는 파일의 이름과 크기를 제공하며, Folder 클래스는 하위 요소들을 관리하고 방문자를 재귀적으로 호출합니다.
- Client:
- 클라이언트는 방문자를 생성하고, 객체 구조의 요소들이 방문자를 수락하도록 요청하여 작업을 수행합니다.
방문자 패턴의 장단점
장점
- 새로운 작업의 추가가 용이:
- 객체 구조를 변경하지 않고도 새로운 작업(Visitor)을 쉽게 추가할 수 있습니다.
- 단일 책임 원칙 준수:
- 객체 구조(Element)와 작업(Visitor)을 분리하여, 각자의 책임을 명확히 분리합니다.
- 객체 구조의 일관성 유지:
- 객체 구조를 변경하지 않고도 다양한 동작을 추가할 수 있습니다.
단점
- 객체 구조 변경의 어려움:
- 객체 구조(Element)가 변경되면, 모든 Visitor 클래스가 영향을 받습니다.
- 복잡성 증가:
- 각 요소(Element)에 대한 방문 메서드를 Visitor 인터페이스에 추가해야 하므로, 요소가 많아질수록 코드가 복잡해질 수 있습니다.
- 캡슐화 위반 가능성:
- Visitor가 객체의 내부 상태에 접근해야 하는 경우, 캡슐화 원칙이 위반될 수 있습니다.
언제 방문자 패턴을 사용해야 할까?
방문자 패턴은 다음과 같은 상황에서 유용합니다.
- 객체 구조는 변경되지 않으면서 새로운 작업을 추가해야 할 때:
- 객체 구조를 자주 변경하지 않고, 새로운 동작만 추가해야 할 때.
- 다양한 객체에 공통된 작업을 수행해야 할 때:
- 서로 다른 타입의 객체에 대해 동일하거나 유사한 작업을 수행해야 할 때.
- 객체 구조와 작업 로직을 분리해야 할 때:
- 객체 구조(Element)와 작업(Visitor)을 독립적으로 유지해야 할 때.
방문자 패턴과 관련 패턴 비교
방문자 패턴은 객체 구조와 작업을 분리하여 동작을 추가할 수 있습니다. 다른 디자인 패턴과의 차이를 살펴보겠습니다.
- 템플릿 메서드 패턴:
- 알고리즘의 골격을 정의하고, 일부 세부 동작을 하위 클래스에서 구현하도록 합니다. 방문자 패턴은 객체 구조에 대해 외부에서 동작을 추가합니다.
- 컴포지트 패턴:
- 객체를 트리 구조로 구성하여 계층적으로 처리합니다. 방문자 패턴은 객체 구조를 유지하면서, 외부에서 작업을 정의합니다.
- 전략 패턴:
- 동작을 캡슐화하고, 런타임에 교체할 수 있도록 합니다. 방문자 패턴은 객체 구조와 무관하게 작업을 추가합니다.
실무에서 방문자 패턴 활용하기
방문자 패턴은 다음과 같은 실무 상황에서 활용됩니다.
- 파일 시스템:
- 파일과 폴더의 크기 계산, 파일 리스트 출력, 검색 등의 작업 구현.
- 컴파일러:
- 구문 트리(AST, Abstract Syntax Tree)에서 코드 분석, 최적화, 코드 생성 등의 작업.
- 그래픽 엔진:
- 도형(점, 선, 사각형 등)에 대해 렌더링, 충돌 처리, 변환 작업.
- 데이터 분석:
- 다양한 데이터 구조(예: JSON, XML)에 대해 데이터 변환, 검증, 추출 작업.
마무리하며
방문자 패턴은 객체 구조와 작업을 분리하여, 새로운 작업을 유연하게 추가할 수 있는 강력한 패턴입니다. 특히, 객체 구조가 복잡하거나 자주 변경되지 않는 시스템에서 큰 효과를 발휘합니다.
객체 구조의 변경 없이 새로운 작업을 추가해야 하는 시스템을 설계할 때, 방문자 패턴을 활용해보세요. 이를 통해 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다.
'Thinking > Concept' 카테고리의 다른 글
템플릿 메서드 패턴(Template Method Pattern) 이해하기 (1) | 2024.11.17 |
---|---|
전략 패턴(Strategy Pattern) 이해하기 (0) | 2024.11.17 |
상태 패턴(State Pattern) 이해하기 (0) | 2024.11.17 |
옵저버 패턴(Observer Pattern) 이해하기 (0) | 2024.11.17 |
메멘토 패턴(Memento Pattern) 이해하기 (1) | 2024.11.16 |