Visitor 패턴

뾰족머리삼돌이·2024년 10월 8일
0

디자인패턴

목록 보기
21/21

이미 잘 구현된 클래스 구조에 공통적인 기능을 추가하려면 어떻게 해야할까?
모든 클래스들에 완벽하게 공통적인 기능이라면 최상위 클래스나 인터페이스에 기본동작을 정의하면 될 것이다
하지만, 세부동작이 상이한 경우에는 하위클래스 각각에 재정의 등을 통해서 메서드를 작성해야 한다.

이는 단기적으로 문제를 해결할 수 있으나, 수정과정에서 예상하지 못한 오류가 발생할 가능성이 높다.
또한, 이러한 유형의 기능이 추가될 때마다 코드가 추가되므로 코드복잡도가 상승하고 SRP를 지키기 힘들어질 것이다.

따라서, 기존의 클래스구조는 최대한 건들지않고 외부에 코드를 작성하는게 좀 더 합리적인 방법이다.
이런 상황에서 도움이 될 수 있는 디자인패턴이 Visitor 패턴이다.


기본적인 아이디어는 외부 클래스에서 메서드 매개변수로 객체를 입력받고, 동작을 수행하는 것이다.
이 과정에서 객체별로 세부동작이 다를 수 있으므로 각 객체에 해당하는 메서드를 구축할 수 있다.

고려해야할 사항은 외부에서 Visitor 클래스의 메서드를 호출할 때, 어떻게 선택하느냐 다.

Visitor 클래스 호출에 입력으로 사용되는 객체집합이 하나의 공통 인터페이스로 관리되는 상황일 때,
단순하게 생각하면 ifinstanceof 를 이용해서 모든 종류의 하위클래스타입을 검사하는 방법이 있다.

for(Node node : graph){
    if (node instanceof City)
        exportVisitor.doForCity((City) node)
    if (node instanceof Industry)
        exportVisitor.doForIndustry((Industry) node)
    // …
}

이는 문제해결에 직관적인 방법이지만, 코드가 깔끔하지 못하다는 단점이 있다.
이 문제를 해결하기 위해서는 주어진 상황을 점검해볼 필요가 있다.

현재 주어진 객체의 타입은 모두 Node이며, ifinstanceof를 사용하고 싶지 않다.
따라서, Visitor의 입장에서 주어진 객체에게 어떤 메서드를 호출해야할지 선별하는게 불가능한 상황이다.

그렇다면 반대로 객체의 입장에서 자신을 처리해줄 Visitor를 선택한다는 구조로 생각해보자
즉, 객체들의 공통메서드로 외부에서 Visitor를 입력받고, 메서드 내에서 Visitor의 메서드를 호출하는 것이다.

// Client code
for(Node node : graph){
    node.accept(exportVisitor)
}

// City
class City{
    void accept(Visitor v){
        v.visit(this);
    }
	// …
}

이 방법을 더블 디스패치라고 부르며, Visitor 패턴에서 주로 사용되는 기법이다.

클래스 구조도를 살펴보면 크게 VisitorElement로 구분된다.
Visitor는 추가해야할 작업이 작성되는 외부 클래스이며, Element는 기존 클래스들을 의미한다.

앞서 설명했던 것처럼 Element에선 accept()를 통해 Visitor를 매개변수로 입력받고, 해당 메서드 내에서 Visitor의 메서드를 호출한다.

각 클래스 내의 메서드에서 Visitor의 메서드를 호출하므로 this를 통해 어떤 클래스타입의 객체가 전달되는지 정확하게 구분이 가능하다. 따라서, Visitor 클래스에서 오버로딩을 통해 구현된 메서드들을 손쉽게 호출할 수 있다.

0개의 댓글

관련 채용 정보