방문자(Visitor)

의도

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

활용성

방문자 패턴은 다음의 경우에 사용한다.

  • 다른 인터페이스를 가진 클래스가 객체 구조에 포함되어 있으며, 구체 클래스에 따라 달라진 연산을 이들 클래스의 객체에 대해 수행하고자 할 때

구조

alt

  • Visitor
    • 객체 구조 내에 있는 각 ConcreteElement 클래스를 위한 Visit() 연산을 선언한다.
      연산의 이름과 인터페이스 형태는 Visit() 요청을 방문자에게 보내는 클래스를 식별한다. 이로써 방문자는 방문된 원소의 구체 클래스를 결정할 수 있다.
      그러고 나서 방문자는 그 원소가 제공하는 인터페이스를 통해 원소에 직접 접근할 수 있다.
  • ConcreteVisitor
    • Visitor 클래스에 선언된 연산을 구현한다. 각 연산은 구조 내에 있는 객체의 대응 클래스에 정의된 일부 알고리즘을 구현한다.
      ConcreteVisitor 클래스는 알고리즘이 운영될 수 있는 상황 정보를 제공하며 자체 상태를 저장한다. 이 상태는 객체 구조를 순회하는 도중 순회 결과를 누적할 때가 많다.
  • Element
    • 방문자를 인자로 받아들이는 Accept() 연산을 정의한다.
  • ConcreteElement
    • 인자로 방문자 객체를 받아들이는 Accept() 연산을 구현한다.

협력 방법

  • 방문자 패턴을 사용하는 사용자는 ConcreteVisitor 클래스의 객체를 생성하고 객체 구조를 따라서 각 원소를 방문하며 순회해야 한다.
  • (방문자가) 구성 원소들을 방문할 때, 구성 원소는 해당 클래스의 Visitor 연산을 호출한다. 이 원소들은 자신을 Visitor 연산에 필요한 인자로 제공하여 (필요하면) 방문자 자신의 상태에 접근할 수 있도록 한다.

결과

방문자 패턴 사용의 장단점

장점
  • Visitor 클래스는 새로운 연산을 쉽게 추가한다.
    • Visitor 클래스는 복잡한 객체를 구성하는 요소에 속한 연산을 쉽게 추가할 수 있다.
      새로운 방문자를 추가하면 객체 구조에 대한 새로운 연산을 추가한 것이 된다.
  • 방문자를 통해 관련된 연산들을 한 군데로 모으고 관련되지 않은 연산을 떼어낼 수 있다.
단점
  • 새로운 ConcreteElement 클래스를 추가하기가 어렵다.
    • 방문자 패턴을 사용하면 Element 클래스에 대한 새로운 서브클래스를 추가하기가 어려워 진다.
      ConcreteElement 클래스가 새로 생길 때마다, Visitor 클래스에 대한 새로운 추상 연산 및 모든 ConcreteVisitor 클래스에 그 연산에 대응하는 구현을 제공해야 한다.

예제 코드

interface CarElementVisitor {
	void visit(Wheel wheel);
	void visit(Engine engine);
	void visit(Body body);
	void visit(Car car);
}

interface CarElement {
	void accept(CarElementVisitor carElementVisitor);
}

class Wheel implements CarElement {
	private String name;
	
	public Wheel(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void accept(CarElementVisitor visitor) {
		visitor.visit(this);
	}
}

class Engine implements CarElement {
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Body implements CarElement {
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Car implements CarElement {
	CarElement[] elements;
	
	public CarElement[] getElements() {
		return elements.clone(); 
	}
	
	public Car () {
		this.elements = new CarElement[]{
			new Wheel("front left"), new Wheel("front right"),
			new Wheel("back left") , new Wheel("back right"),
			new Body(), new Engine()
		};
	}
	
	public void accept(CarElementVisitor visitor) {
		for (CarElement element : this.getElements()) {
			element.accept(visitor);
		}
		
		 visitor.visit(this);
	}
}

class CarElementPrintVisitor implements CarElementVisitor {
	public void visit(Wheel wheel) {
		System.out.println("Visiting "+ wheel.getName() + " wheel");
	}

	public void visit(Engine engine) {
		System.out.println("Visiting engine");
	}

	public void visit(Body body) {
		System.out.println("Visiting body");
	}

	public void visit(Car car) {
		System.out.println("Visiting car");
	}
}

class CarElementDoVisitor implements CarElementVisitor {
	public void visit(Wheel wheel) {
		System.out.println("Kicking my "+ wheel.getName() + " wheel");
	}

	public void visit(Engine engine) {
		System.out.println("Starting my engine");
	}

	public void visit(Body body) {
		System.out.println("Moving my body");
	}

	public void visit(Car car) {
		System.out.println("Starting my car");
	}
}

public class VisitorDemo {
	static public void main(String[] args){
		Car car = new Car();
		car.accept(new CarElementPrintVisitor());
		car.accept(new CarElementDoVisitor());
	}
}