객체 구조를 이루는 원소에 대해 수행할 연산을 표현합니다. 연산을 적용할 원소의 클래스를 변경하지 않고도 새로운 연산을 정의 할 수 있게 합니다.
프로그램을 추상 구문 트리로 표현하는 컴파일러를 생각해 봅시다. 모든 변수들이 정의 되었는지를 점검하는 등 정적의미(static semantic) 분석을 위한 연산을 수행할 필요가 있을 것입니다. 추상 구문 트리는 타입점검, 코드 최적화, 흐름 분석, 변수 검사 등 필요한 연산을 정의해야 할 것입니다. 게다가, 장식인쇄(pretty-printing), 프로그램 재구조화, 코드 삽입은 물론 프로그램의 여러 가지 측정 값을 계산하는 데에도 추상 구문 트리를 사용할 수 있을 것입니다.
앞 다이어그램은 Node 클래스 계통의 일부를 보여줍니다.
지금 문제는 연산들이 여러 노드 클래스에 걸쳐 분산되어 있어 시스템의 이해 및 유지보수, 변경 작업이 어렵다는 점입니다.
연산 하나를 새로 추가하려면 관련된 모든 클래스를 재컴파일해야 할 때도 태반입니다.
이에 대한 해결책은 각 클래스에서 서로 관련된 연산들을 추려 모아 별도로 하나의 객체로 묶습니다. 이런 객체를
가리켜 방문자라고 합니다. 그리고 이 방문자 객체를 추상 구문 트리의 원소에 전달하여 순회시키는 것입니다. 트
리의 원소가 방문자를 수락(accept) 하면 그것이 클래스를 인코딩하는 방문자에게 요청을 보냅니다. 방문자는 그
원소도 인자로 포함합니다.
만들 컴파일러가 방문자를 사용하여 프로시저의 타입 점검을 수행한다면, 컴파일러 타입 점검과 관련된 연산은
TypeCheckingVisitor 객체를 생성하고 추상 구문 트리에 대해 Accept() 연산을 호출하면서 그 연산의 인자로
TypeCheckingVisitor 객체를 넘길 것입니다. 각 노드는 그 방문자 객체의 연산을 다시 호출하는 것으로
Accept()를 구현 합니다.
방문자 패턴을 사용하면 두 개의 클래스 계통이 정의됩니다. 하나는 연산이 적용되는 원소에 대한 클래스 계통
(Node 클래스 계통)이고, 또 하나는 그 원소에 대해 적용할 연산을 정의하는 바문자 클래스 계통(NodeVisitor 클
래스계통)입니다. 새로운 연산을 추가하려면 방문자 클래스 계통에 새로운 서브c클래스를 추가하면 됩니다.
방문자 패턴을 사용하면서 얻는 이익과 부담을 정리하면 다음과 같습니다.
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());
}
}
복합체 패턴이 정의하는 복합 객체 구조에 대해 연산을 적용하는 데에 방문자를 쓸수 있습니다.
방문자 패턴은 해석자 패턴의 해석과정에도 사용할 수 있습니다.