Pattern - Visitor
인생은 항상 적절한 안배가 필요하다.
삶은 언제나 갑자기 다른 방향으로 바뀌어나갈 수 있기 때문에..
의도
객체 구조를 이루는 원소에 대해 수행할 연산을 표현합니다. 연산을 적용할 원소의 클래스를 변경하지 않고도 새로운 연산을 정의할 수 있게 합니다.
동기
- 프로그램을 추상 구문 트리로 표현하는 컴파일러를 생각해봅시다. 이 추상 구문 트리는 모든 변수들이 정의되었는지를 점검하는 등 “정적 의미” 분석을 위한 연산 을 수행할 필요가 있을 것입니다. 또한 이 트리를 바탕으로 코드를 생성할 필요도 있습니다. 그렇기 때문에 , 추상 구문 트리는 타입 점검, 코드 최적화, 흐름 분석, 변수 검사등 필요한 연산을 정의해야 할 것입니다. 게다가 장식 인쇄, 프로그램 재구조화, 코드 삽입은 물론 프로그램의 여러 가지 측정 값을 계산하는 데에도 추상 구문 트리를 사용할 수 있을 것입니다.
- 이 연산의 대부분은 대입문을 표현하는 노드, 변수 혹은 수식을 나타내는 노드를 각각 별도로 처리해야 합니다. 이를 위해서는 대입문을 위한 하나의 클래스가 있게 되고, 변수 접근을 위한 또 하나의 클래스 등이 있어야 할 것입니다. 이런 노드 클래스 집합은 물론 컴파일될 언어에 따라 달라지겠지만 주어진 언어에 대해 변경이 크거나 한 것은 아닙니다.
- 연산들이 여러 노드에 걸쳐 분산되어 있을 경우, 시스템의 이해 및 유지보수 변경 작업이 어렵다는 점입니다. 타입 점검 코드가 장식 인쇄 코드 또는 흐름 분석 코드와 섞이면 대단히 혼란스러워집니다. 게다가, 연산하나를 새로 추가하려면 관련된 모든 클래스를 재컴파일해야 할 때도 태반입니다. 이럴 때 새로운 연산을 별도로 추가할 수 있으며, 추가된 연산에 상관 없이 노드 클래스들이 남아 있다면 아주 좋을 것입니다.
- 각 노드 클래스에서 서로 관련된 연산을 추려 모아 별도의 하나의 객체로 묶습니다. 이런 객체를 가리켜 방문자라고 합니다. 그리고 이 방문자 객체를 추상 구문 트리의 원소에 전달하여 순회시키는 것입니다. 트리의 원소가 방문자를 “수락(Accept)” 하면, 그것의 클래스를 인코딩 하는 방문자에게 요청을 보냅니다. 방문자는 그 원소도 인자로 포함합니다. 여기까지 모두 끝난 후, 방문자는 그 원소를 위한 연산, 즉 그 원소의 클래스 안에 있는 연산을 실행합니다.
활용성
- 다른 인터페이스를 가진 클래스가 객체 구조에 포함되어 있으며, 구체 클래스에 따라 달라진 연산을 이들 클래스의 객체에 대해 수정하고자 할 때
- 각각 특징이 있고, 관련되지 않은 많은 연산이 한 객체 구조에 속해있는 객체들에 대해 수행될 필요가 있으며, 연산으로 클래스들을 “더럽히고” 싶지 않을 때, Visitor 클래스는 관련된 모든 연산을 하나의 클래스 안에다 정의해 놓음으로써 관련된 연산이 함께 있을 수 있게 해줍니다. 어떤 객체 구조가 많은 응용 프로그램으로 공유될 때, Visitor 클래스를 사용하면 이 객체 구조가 필요한 응용 프로그램에만 연산을 둘 수 있습니다
- 객체 구조를 정의한 클래스는 거의 변하지 않지만, 전체 구조에 걸쳐 새로운 연산을 추가하고 싶을 때. 객체 구조를 변경하려면 모든 방문자에 대한 인터페이스를 재정의해야 하는데, 이 작업에 잠재된 비용이 클 수 있습니다. 객체 구조가 자주 변경 될 때는 해당 연산을 클래스에 정의하는 편이 더 낫습니다.
항목에 대한 설명
-
Visitor
Node Visitor
객체 구조에 있는 각 ConcreteElement 클래스를 위한 Visit() 연산을 선언합니다. 연산의 이름과 인터페이스 형태는 Visit() 요청을 방문자에게 보내는 클래스를 식별합니다. 이로써 방문자는 방문된 원소의 구체 클래스를 결정할 수 있습니다. 그러고 나서 방문자는 그 원소가 제공하는 인터페이스를 통해 원소에 직접 접근할 수 있습니다. -
Concrete Visitor
Visistor
클래스에 선언된 연산을 구현합니다. 각 연산은 구조내에 있는 객체의 대응 클래스에 정의된 일부 알고리즘을 구현합니다. ConcreteVisitor 클래스는 알고리름이 운영될 수 있는 상황 정보를 제공하며 자체 상태를 저장힙니다. 이 상태는 객체구조를 순회하는 도중에 순회 결과를 누적할 때가 많습니다. -
Element
Node
방문자를 인자로 받아들이는 Accept() 연산을 구현합니다. -
ConcreteElement
AssignmentNode, VariableRefNode
인자로 방문자 객체를 받아들이는 Accept() 연산을 구현합니다. -
ObjectStructure
Program
객체 구조 내의 원소들을 나열할 수 있습니다. 방문자가 이 원소에 접근하게 하는 상위 수준 인터페이스를 제공합니다. Object-Structure는 Composite 패턴으로 만든 복합체일 수도 있고, 리스트(list) 나 집합(set) 등 컬렉션일 수도 있습니다.
항목에 대한 설명
- 방문자 패턴을 사용하는 사용자는 ConcreteVisitor 클래스의 객체를 생성하고 객체 구조를 따라서 각 원소를 방문하며 순회해야 합니다.
- 구성 원소들을 방문할 때, 구성 원소는 해당 클래스의 Visitor 연산을 호출합니다. 이 원소들은 자신을 Visitor 연산에 필요한 인자로 제공하여 방문자 자신의 상태에 접근할 수 있도록 합니다.
결과
- Visitor 클래스는 새로운 연산을 새롭게 추가합니다.
- 방문자를 통해 관련된 연산들을 한 군데로 모으고 관련되지 않은 연산을 떼어 낼 수 있습니다.
- 새로운 ConcreteElement 클래스를 추가하기가 어렵습니다.
- 클래스 계층 구조에 걸쳐서 방문합니다.
- 상태를 누적할 수 있습니다.
- 데이터 은닉을 깰 수 있습니다.
코드 예제
package DesignPattern.gof_visitor.sample04;
import DesignPattern.gof_visitor.sample04.Visitor.InputPrintVisitor;
import DesignPattern.gof_visitor.sample04.Visitor.LoadPrintVisiter;
import DesignPattern.gof_visitor.sample04.Visitor.OutputPrintVisitor;
import DesignPattern.gof_visitor.sample04.element.Caller;
import DesignPattern.gof_visitor.sample04.element.POS;
public class PrintDemo {
public static void main(String[] args) {
Caller caller = new Caller();
caller.accept(new InputPrintVisitor());
caller.accept(new LoadPrintVisiter());
caller.accept(new OutputPrintVisitor());
}
}
public interface PrintVisitor {
public void visit(Front front );
public void visit(POS pos);
public void visit(WEB web);
public void visit(Kiosk kiosk);
}
public class OutputPrintVisitor implements PrintVisitor {
@Override
public void visit(Front front) {
System.out.println("프론트 출력했습니다.");
}
@Override
public void visit(POS pos) {
System.out.println("포스를 출력했습니다.");
}
@Override
public void visit(WEB web) {
System.out.println("웹을 출력했습니다.");
}
@Override
public void visit(Kiosk kiosk) {
}
}
public class LoadPrintVisiter implements PrintVisitor
{
@Override
public void visit(Front front) {
System.out.println("프론트 데이터를 호출했습니다.");
}
@Override
public void visit(POS pos) {
System.out.println("포스 데이터를 호출했습니다.");
}
@Override
public void visit(WEB web) {
System.out.println("웹 데이터를 호출했습니다.");
}
@Override
public void visit(Kiosk kiosk) {
}
}
public class InputPrintVisitor implements PrintVisitor {
@Override
public void visit(Front front) {
System.out.println("프론트 데이터를 입력했습니다.");
}
@Override
public void visit(POS pos) {
System.out.println("포스 데이터를 입력했습니다.");
}
@Override
public void visit(WEB web) {
System.out.println("웹 데이터를 입력했습니다.");
}
@Override
public void visit(Kiosk kiosk) {
}
}
public interface PrinterElement {
public void accept(PrintVisitor printVisitor);
}
public class Caller implements PrinterElement {
List<PrinterElement> printList;
public Caller(){
printList = new ArrayList<>();
printList.add(new Front());
printList.add(new POS());
printList.add(new WEB());
}
@Override
public void accept(PrintVisitor printVisitor) {
printList.forEach( value -> {
value.accept(printVisitor);
});
}
}
public class WEB implements PrinterElement {
@Override
public void accept(PrintVisitor printVisitor) {
printVisitor.visit(this);
}
}
public class POS implements PrinterElement {
@Override
public void accept(PrintVisitor printVisitor) {
printVisitor.visit(this);
}
}
public class Kiosk implements PrinterElement {
@Override
public void accept(PrintVisitor printVisitor) {
printVisitor.visit(this);
}
}
public class Front implements PrinterElement {
@Override
public void accept(PrintVisitor printVisitor) {
printVisitor.visit(this);
}
}