Visitor[Design Pattern]

SnowCat·2023년 4월 5일
0

Design Pattern

목록 보기
23/23
post-thumbnail

의도

  • 비지터 => 알고리즘을 작동하는 객체로부터 분리할 수 있도록 하는 행동 디자인 패턴

문제

  • 거대한 그래프로 구성된 지도를 사용한 앱을 개발하고 있다.
  • 그래프의 각 노드는 도시와 같은 복잡한 객체를 나타낼 수 있고, 관광지와 같은 세부적인 항목 역시도 포함할 수 있다. 도로가 있으면 노드들은 서로 연결되며, 각 노드 유형은 자체적인 클래스를 가지고 각각의 노드는 객체이다.
  • 어느날 그래프를 XML 형식으로 내보내고자 한다. 노드 클래스에 내보내기 메서드를 추가해 재귀를 사용해 각 그래프의 노드에 작업을 진행해주면 간단하게 해결할 수 있다.
  • 그런데 제품의 발매일이 얼마 남지 않았고, 시스템의 노드 클래스를 건드리는 것은 치명적인 버그를 불러일으킬 수 있기 때문에 쉽게 작업을 진행할수가 없는 상황이다.

해결책

  • 새로운 행동을 기존 클래스에 통합하는 대신 visitor라는 별도에 클래스에 배치
  • 행동을 수행하는 객체는 visitor 메서드 중 하나에 인수로 전달되 원래 객체 내의 데이터에 접근할 수 있게 됨
  • 객체가 여러개가 되어 메서드가 복잡해지는 경우에는 둘 중 하나의 방법을 사용
    1. 메서드 내부에 조건문을 사용해 객체를 검사 (메서드 오버로딩은 비지터가 정확한 클래스를 사전에 알 수 없기 때문에 불가능), 이 경우 코드가 복잡해질 수 있음
    2. 객체에 메서드를 하나 추가해 어떤 비지터 클래스를 사용할 것인지 결정하게 함, 이 경우 결국 코드를 수정해야 함

구조

/**
 * base visitor를 받는 컴포넌트 인터페이스 선언
 */
interface Component {
    accept(visitor: Visitor): void;
}

/**
 * 각각의 컴포넌트에서 accept 메서드를 구현, 구현은 컴포넌트마다 달라짐
 */
class ConcreteComponentA implements Component {
    public accept(visitor: Visitor): void {
        visitor.visitConcreteComponentA(this);
    }

    /**
     * 추가적인 메서드를 가질 수 있으며, 이는 visitor 역시 구체적인 클래스를 알고 있는 상황에서 사용 가능
     */
    public exclusiveMethodOfConcreteComponentA(): string {
        return 'A';
    }
}

class ConcreteComponentB implements Component {
    public accept(visitor: Visitor): void {
        visitor.visitConcreteComponentB(this);
    }

    public specialMethodOfConcreteComponentB(): string {
        return 'B';
    }
}

/**
 * 구체적인 컴포넌트를 방문하는 메서드를 정의하는 Visitor 인터페이스
 */
interface Visitor {
    visitConcreteComponentA(element: ConcreteComponentA): void;

    visitConcreteComponentB(element: ConcreteComponentB): void;
}

/**
 * 같은 알고리즘의 여러가지 버전이 존재하며, 이는 방문할 컴포넌트에 따라 조금씩 달라지게 됨
 */
class ConcreteVisitor1 implements Visitor {
    public visitConcreteComponentA(element: ConcreteComponentA): void {
        console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`);
    }

    public visitConcreteComponentB(element: ConcreteComponentB): void {
        console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`);
    }
}

class ConcreteVisitor2 implements Visitor {
    public visitConcreteComponentA(element: ConcreteComponentA): void {
        console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`);
    }

    public visitConcreteComponentB(element: ConcreteComponentB): void {
        console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`);
    }
}

/**
 * 클라이언트 코드
 */
function clientCode(components: Component[], visitor: Visitor) {
    // ...
    for (const component of components) {
        component.accept(visitor);
    }
    // ...
}

const components = [
    new ConcreteComponentA(),
    new ConcreteComponentB(),
];

console.log('The client code works with all visitors via the base Visitor interface:');
const visitor1 = new ConcreteVisitor1();
clientCode(components, visitor1);
/*
A + ConcreteVisitor1
B + ConcreteVisitor1
*/
console.log('');

console.log('It allows the same client code to work with different types of visitors:');
const visitor2 = new ConcreteVisitor2();
clientCode(components, visitor2);
/*
A + ConcreteVisitor2
B + ConcreteVisitor2
*/

적용

  • 복잡한 객체 구조의 모든 요소에 대해 작업을 수행해야 할 때 사용
    비지터 패턴을 사용해 모든 대상 클래스에 같은 작업을 여러번 반복할 수 있도록 해줌
  • 보조 행동들의 비지니스 로직을 정리하고자 할 때 사용
    주 클래스에서 주된 작업을 제외한 모든 행동들을 비지터 클래스의 집합으로 추출하게 됨
  • 특정 행동이 클래스 계층구조의 일부 클래스에게만 의미가 있을 때 사용
    별도의 비지터 클래스로 행동을 추출하고 관련 클래스의 객체들을 수락하는 비지터 메서드들만 구현을 하면 됨

구현 방법

  1. 프로그램에 존재하는 각 구상 요소 클래스별로 하나씩 visitor 메서드를 만들고, 메서드들의 집합을 인터페이스로 선언
  2. 엘리먼트 인터페이스를 선언, 이 때 기존의 element class 계층구조와 적업하면 추상 클래스 레벨에 accept 메서드를 추가
  3. 모든 구상 엘리먼트 클래스에서 accept 메서드를 구현하고, 비지트 메서드에 대한 호출을 input으로 들어오는 비지터 객체에 리다이렉트를 시킴, 이 때 엘리먼트 클래스는 비지터와 비지터 인터페이스를 통해서만 작동하게 되며 비지터 클래스는 비지터 메서드에 참조된 모든 엘리먼트 클래스를 알고 있어야 함
  4. 각 객체에서 구현할수 없는 행동들은 새로운 구상 비지터 클래스를 만들고 모든 비지터 메서드를 구현함
  5. 클라이언트에서 비지터 객체들을 만들고 accept 메서드를 통해 비지터를 전달하게 됨

장단점

  • 다른 클래스를 변경하지 않으면서 해당 클래스의 객체와 작동할 수 있는 새로운 행동을 도입할 수 있기 때문에 개방, 폐쇄 원칙 준수
  • 같은 행동의 여러 버전을 같은 클래스로 이동해 단일 책임 원칙 준수
  • 비지터 객체는 다양한 객체들과 작업하면서 여러 정보를 축적할 수 있음
  • 클래스가 계층구조에 추가되거나 제거될 때 모든 비지터를 업데이트 해야 함
  • 비지터들은 함께 작업하는 엘리먼트들에 대한 접근권한이 부족할 수 있음
profile
냐아아아아아아아아앙

0개의 댓글