[디자인 패턴] 방문자 패턴_ Visitor pattern (JavaScript)

2024. 11. 17. 21:50Web_Programming

Visitor 패턴이란?

Visitor 패턴은 객체 구조를 변경하지 않고도 새로운 연산을 추가할 수 있도록 하는 행동 디자인 패턴입니다. 데이터 구조와 알고리즘의 분리를 통해, 데이터 객체는 그대로 두고 외부에서 로직을 확장할 수 있습니다.

구성요소

Visitor 인터페이스

  • 데이터 구조의 각 요소에 대해 수행될 연산을 선언합니다.
  • 메서드 이름과 매개변수는 구체적인 요소 타입에 따라 달라집니다. 
  • interface Visitor { visitConcreteElementA(element: ConcreteElementA): void; visitConcreteElementB(element: ConcreteElementB): void; }

ConcreteVisitor

  • Visitor 인터페이스를 구현하며, 각 요소 타입에 대해 구체적인 작업을 수행합니다.
  • 예: 알림 전송, 로그 기록 등
    class ConcreteVisitorA implements Visitor {
        visitConcreteElementA(element: ConcreteElementA): void {
            console.log("VisitorA processing ElementA");
        }
        visitConcreteElementB(element: ConcreteElementB): void {
            console.log("VisitorA processing ElementB");
        }
    }

Element 인터페이스

  • Visitor를 받아들이는 역할로, accept(visitor: Visitor) 메서드를 정의합니다.
    interface Element {
        accept(visitor: Visitor): void;
    }

ConcreteElement

  • Element 인터페이스를 구현하며, accept 메서드에서 Visitor 객체를 호출합니다.
    class ConcreteElementA implements Element {
        accept(visitor: Visitor): void {
            visitor.visitConcreteElementA(this);
        }
    }

예시 코드

주문 처리 시스템을 예시로, 

  • 각 주문 상태(Pending, Shipped 등)를 ConcreteElement로 표현합니다.
  • 알림(Notification)과 로깅(Logging) 작업을 수행할 방문자 클래스를 구현합니다.
// Element 인터페이스 및 구현체
interface Order {
  accept(visitor: OrderVisitor): void;
}

class PendingOrder implements Order {
  constructor(public orderId: string) {}
  accept(visitor: OrderVisitor): void {
    visitor.visitPendingOrder(this);
  }
}

class ShippedOrder implements Order {
  constructor(public orderId: string, public trackingNumber: string) {}
  accept(visitor: OrderVisitor): void {
    visitor.visitShippedOrder(this);
  }
}

// Visitor 인터페이스 및 구현체
interface OrderVisitor {
  visitPendingOrder(order: PendingOrder): void;
  visitShippedOrder(order: ShippedOrder): void;
}

class NotificationVisitor implements OrderVisitor {
  visitPendingOrder(order: PendingOrder): void {
    console.log(`Notifying pending order: ${order.orderId}`);
  }
  visitShippedOrder(order: ShippedOrder): void {
    console.log(`Notifying shipped order: ${order.orderId} (Tracking: ${order.trackingNumber})`);
  }
}

// 사용
const orders: Order[] = [
  new PendingOrder("O-001"),
  new ShippedOrder("O-002", "TRACK-123"),
];

const notificationVisitor = new NotificationVisitor();
orders.forEach(order => order.accept(notificationVisitor));

사용 상황

  • 객체 구조는 변경되지 않고 그대로 유지해야 하지만, 새로운 동작을 추가해야 할 때.
  • 데이터와 관련 연산이 서로 다른 클래스로 분리되어야 할 때.
  • 작업 대상이 복잡한 객체 구조(예: 트리 구조)일 때.

장점

  1. 연산 추가가 용이
    • 데이터 구조를 수정하지 않고도 새로운 동작(연산)을 추가할 수 있습니다.
  2. 데이터와 연산의 분리
    • 데이터 객체는 자료 구조에 집중하고, 알고리즘은 Visitor 객체로 분리하여 유연성을 높입니다.
  3. 코드 확장성
    • 다양한 Visitor를 생성해 기존 데이터 구조에 새로운 동작을 쉽게 추가 가능.

단점

  1. 높은 결합도
    • Visitor와 Element 간의 의존도가 높아집니다.
  2. 새로운 요소 추가의 어려움
    • 새로운 데이터 구조(ConcreteElement)가 추가될 경우, 모든 Visitor에 수정이 필요합니다.
반응형