Visitor

GamSa Ham·2022년 11월 15일
0

GoF디자인패턴

목록 보기
18/22

의도

객체 구조를 이루는 원소에 대해 수행할 연산을 표현합니다. 연산을 적용할 원소의 클래스를 변경하지 않고도 새로운 연산을 정의 할 수 있게 합니다.

동기

프로그램을 추상 구문 트리로 표현하는 컴파일러를 생각해 봅시다. 모든 변수들이 정의 되었는지를 점검하는 등 정적의미(static semantic) 분석을 위한 연산을 수행할 필요가 있을 것입니다. 추상 구문 트리는 타입점검, 코드 최적화, 흐름 분석, 변수 검사 등 필요한 연산을 정의해야 할 것입니다. 게다가, 장식인쇄(pretty-printing), 프로그램 재구조화, 코드 삽입은 물론 프로그램의 여러 가지 측정 값을 계산하는 데에도 추상 구문 트리를 사용할 수 있을 것입니다.

앞 다이어그램은 Node 클래스 계통의 일부를 보여줍니다.

지금 문제는 연산들이 여러 노드 클래스에 걸쳐 분산되어 있어 시스템의 이해 및 유지보수, 변경 작업이 어렵다는 점입니다.

연산 하나를 새로 추가하려면 관련된 모든 클래스를 재컴파일해야 할 때도 태반입니다.

이에 대한 해결책은 각 클래스에서 서로 관련된 연산들을 추려 모아 별도로 하나의 객체로 묶습니다. 이런 객체를

가리켜 방문자라고 합니다. 그리고 이 방문자 객체를 추상 구문 트리의 원소에 전달하여 순회시키는 것입니다. 트

리의 원소가 방문자를 수락(accept) 하면 그것이 클래스를 인코딩하는 방문자에게 요청을 보냅니다. 방문자는 그

원소도 인자로 포함합니다.

만들 컴파일러가 방문자를 사용하여 프로시저의 타입 점검을 수행한다면, 컴파일러 타입 점검과 관련된 연산은

TypeCheckingVisitor 객체를 생성하고 추상 구문 트리에 대해 Accept() 연산을 호출하면서 그 연산의 인자로

TypeCheckingVisitor 객체를 넘길 것입니다. 각 노드는 그 방문자 객체의 연산을 다시 호출하는 것으로

Accept()를 구현 합니다.

방문자 패턴을 사용하면 두 개의 클래스 계통이 정의됩니다. 하나는 연산이 적용되는 원소에 대한 클래스 계통

(Node 클래스 계통)이고, 또 하나는 그 원소에 대해 적용할 연산을 정의하는 바문자 클래스 계통(NodeVisitor 클

래스계통)입니다. 새로운 연산을 추가하려면 방문자 클래스 계통에 새로운 서브c클래스를 추가하면 됩니다.

활용성

  • 다른 인터페이스를 가진 클래스가 객체 구조에 포함되어 있을때
  • 각각 특징이 있고, 관련되지 않은 많은 연산이 한 객체 구조에 속해있는 객체들에 대해 수행될 필요가 있으며, 연산으로 클래스들을 “더럽히고” 싶지 않을 때
  • 객체 구조를 정의한 클래스는 거희 변하지 안지만, 전체 구조에 걸쳐 새로운 연산을 추가하고 싶을때

구조

참여자

  • Visitor(NodeVisitor): 객체 구조 내에 있는 각 ConcreteElement 클래스를 위한 Visit() 연산을 수행합니다.
  • ConcreteVisitor(TypeCheckingVisitor): Visitor 클래스에 선언된 연산을 구현합니다.
  • Element(Node): 방문자를 인자로 받아 들이는 Accept() 연산을 수행합니다.
  • ConcreteElement(AssignmentNode, VariableRefNode): 인자로 방문자 객체를 받아들이는 Accept() 연산을 구현합니다.
  • ObjectStructure(Program): 객체 구조의 원소들을 나열할 수 있습니다.

결과

방문자 패턴을 사용하면서 얻는 이익과 부담을 정리하면 다음과 같습니다.

  1. Visitor 클래스는 새로운 연산을 쉽게 추가합니다.
  2. 방문자를 통해 관련된 연산들을 한 군데로 모으고 관련되지 않은 연산을 떼어낼 수 있습니다.
  3. 새로운 ConcreteElement 클래스를 추가하기가 어렵습니다.
  4. 클래스 계층 구조에 걸쳐서 방문합니다.
  5. 상태를 누적할 수 있습니다.
  6. 데이터 은닉을 꺨 수 있습니다.

구현

package study.designpattern;

class Watt {}
class Currency {
    public void add(Currency netPrice) {
    }
}

class Equipment {

    public String getName() {
        return name;
    }

    public Watt Power() {
        return null;
    }

    public Currency netPrice() {
        return null;
    }

    public Currency discountPrice() {
        return null;
    }

    public void accept(EquipmentVisitor visitor) {

    }

    private String name;

}

interface EquipmentVisitor {
     void visitFloppyDist(FloppyDisk e);
    void visitCard(Card e );
    void visitChassis(Chassis e);
    void visitBus(Bus e);
}

class FloppyDisk extends Equipment{
    @Override
    public void accept(EquipmentVisitor visitor) {
        visitor.visitFloppyDist(this);
    }
}

class Card extends Equipment{
    @Override
    public void accept(EquipmentVisitor visitor) {
        visitor.visitCard(this);
    }
}
class Chassis extends Equipment{
    @Override
    public void accept(EquipmentVisitor visitor) {
        visitor.visitChassis(this);
    }

}
class Bus extends Equipment{
    @Override
    public void accept(EquipmentVisitor visitor) {
        visitor.visitBus(this);
    }
}

// 확장됩니다.
class PricingVisitor implements EquipmentVisitor {

    public Currency getTotalPrice() {
        return null;
    }

    @Override
    public void visitFloppyDist(FloppyDisk e) {
        total.add(e.netPrice());
    }

    @Override
    public void visitCard(Card e) {
        total.add(e.discountPrice());
    }

    @Override
    public void visitChassis(Chassis e) {
        // ...
    }

    @Override
    public void visitBus(Bus e) {
        // ...
    }

    private Currency total;
}

class Inventory {

    public void accumulate(Equipment e) {
    }
}

class InventoryVisitor implements EquipmentVisitor {

    public Inventory getInventory() {
        return inventory;
    }

    @Override
    public void visitFloppyDist(FloppyDisk e) {
        inventory.accumulate(e);
    }

    @Override
    public void visitCard(Card e) {
        inventory.accumulate(e);
    }

    @Override
    public void visitChassis(Chassis e) {
        // ...
    }

    @Override
    public void visitBus(Bus e) {
        // ...
    }

    private Inventory inventory;
}

public class VisitorPattern {

    public static void main(String[] args) {

        Equipment component = new Equipment();
        InventoryVisitor visitor = new InventoryVisitor();

        component.accept(visitor);

        System.out.println(component.getName());
        System.out.println(visitor.getInventory());

    }

}

관련 패턴

복합체 패턴이 정의하는 복합 객체 구조에 대해 연산을 적용하는 데에 방문자를 쓸수 있습니다.

방문자 패턴은 해석자 패턴의 해석과정에도 사용할 수 있습니다.

profile
안녕하세요. 자바를 좋아하고 디자인 패턴, Refactoring, Clean Code에 관심이 많은 백엔드 개발자입니다.

0개의 댓글