

비지터 패턴(Visitor Pattern, 방문자 패턴)은 기존에 작동하는 객체들로부터 알고리즘들을 분리할 수 있는 행동 패턴이에요.
예를 들면 HTML DOM tree로 만들어진 객체를 PDF로 출력하는 기능을 만들어야 한다고 가정해봐요. DOM tree의 각 노드를 알맞게 PDF 객체로 만들기 위해서 변환하는 로직은 DOM tree의 객체에 추가하면 많은 기능을 담당하게 되고 코드도 복잡해지며 의존성도 많이 생겨요.
하지만 이 알고리즘들을 비지터 패턴으로 구현한다면 비지터 객체에서 노드에 맞게 알고리즘들을 구현하여 따로 분리할 수 있어요. DOM 트리고 별도의 알고리즘을 추가할 필요가 없어져요.
비지터 패턴은 객체 구조(Element)와 그 객체들에 대해 수행할 행위(Visitor)를 분리하는 패턴이에요.

Element (방문 대상 인터페이스)
역할 : 방문될 수 있는 객체의 공통 인터페이스 역할을 해요. accept 메서드로 Visitor 인터페이스를 받아드려요.
특징 :
accept 메서드로 자기 자신을 visitor에 넘겨요.
Element는 Visitor가 무슨 작업을 하는지 몰라요.
예시 : AST의 노드, HTML의 노드
Visitor (행위 인터페이스)
역할 : 각 Element 타입에 맞게 수행할 알고리즘을 정의해요.
특징 :
Element 종류가 늘어나면 Visitor 인터페이스도 수정되요.
Visitor 인터페이스에서 모든 Element에 대한 처리 함수가 구현되요.
Double Dispatch로 메서드 디스패치를 두 번 사용하여 타입 유연성을 높여요. (단순히 visit 함수가 Element 인터페이스가 아닌 구상 클래스에 의존하여 정확히 맞는 타입의 함수를 실행하도록 보장)
예시 : AST Parser, HTML Renderer
ConcreteElement (실제 방문 대상)
역할 : 실제 방문하는 객체로 타입에 맞게 visitor.visit(this)를 호출해요. (Element 타입이 아닌 ConcreteElement 타입임을 확실히 전달)
특징
기존 메인 로직은 그대로 사용해요.
비지터에서 알고리즘들이 사용되기 때문에 행위가 수정되어도 클래스는 수정하지 않아요.
이미지와 텍스트로 이루어진 문서 편집기가 있고, 해당 편집기에 HTML 변환 기능과 글자 수 계산 기능을 추가하고 싶다고 가정해봐요.
문서 편집기 Element
Text
Image
문서 편집기 기능
HTML 변환
글자 수 계산
Element (방문 대상 인터페이스) | Concrete Element (방문 대상 클래스) |
|---|---|
| |
Visitor (방문 대상에 맞게 행동하는 인터페이스) | Concrete Visitors (Visitor 구현체) |
| |
Client (사용 코드) | |
| |
위 구조를 보면 document의 문서 구조는 그대로 사용하면서, visitor만 추가하여 기능을 만들고 있어요. document의 문서 구조나 Element의 로직은 전혀 건드리지 않음을 알 수 있어요.
장점
기능 확장이 쉬워요. (OCP)
관련 로직이 한 곳에 모여요. (글자 수 세기 관련은 글자 수 세기 비지터에만 존재)
Double Dispatch를 활용하여 타입 분기(instance of) 코드가 없이 깔끔하게 구현할 수 있어요.
복잡한 객체 구조 순회에서 로직에 집중할 수 있어요.
단점
Element 타입 추가에 취약해요.
구조가 단순할 때 너무 과해요.
Visitor는 Element 인터페이스에 의존하지 않고 Concrete Element에 의존하여 캡슐화가 약해져요.
구조 이해가 어려울 수 있어요.
Visitor 패턴을 끝으로 Head First Design Patterns 책을 마무리 지었어요. 저와 처음부터 디자인 패턴을 함께 공부하셨다면 이제는 상황에 맞게 패턴을 고려해볼 수 있는 사람이라고 생각해요.
저는 그래요. 처음 이 책을 읽고 패턴을 막 적용해보려고 했어요. 그런데 실제 비즈니스 로직을 엄청 복잡한 경우가 많고 예외도 많아서 그대로 적용하기는 힘들어요. 특히나 프레임 워크에 영향을 많이 받거나, 언어의 영향을 받으면서 패턴을 적용하기 더 힘들어요. 결국 진짜 중요한 것은 "어떤 문제가 생겨서 이러한 패턴을 사용했고 무엇을 해결했는가"에요. 그러면 React에서 어떤 페이지에서 많은 기능들이 사용되어 코드가 지저분하고 코드 분리가 안된거 같다는 생각이 들때, "퍼사드 패턴으로 묶어서 추상화해서 제공하는 것이 좋을지? 아니면 일부러 로직들을 노출 시켜서 바로 확인할 수 있는 것이 더 이점을 가질지? 컴포넌트를 분리하는게 나을지?" 본인만의 생각과 근거로 저울질 할 수 있게 되요.
최종적으로는 어떤 패턴을 사용할 지가 아니라 무슨 문제가 있을지, 혹은 무슨 트레이드오프가 생길지를 고민할 수 있게 되고, 그에 맞게 어떤 식으로 코드를 구현하는게 좋을지 자연스럽게 코드를 짤 수 있을 거라 생각해요.
저도 코드를 잘 짜는건 아니지만, 잘 짜려고 노력해요. 실제로 코드를 짜려할 때 항상 고민하다 시간을 많이 쓰는 거 같에요. 오히려 생산성이 많이 떨어져서 이 생산성 때문에 고민하는 시간을 일부러 줄이기도 해요. 어느 순간에 저도 숨쉬듯이 알맞다고 생각되는 구조로 빠르게 코드를 짤 수 있으면 좋겠어요.