Visitor는 사전적인 의미로 어떤 장소에 찾아오는 사람이라는 의미를 갖고 있다. 방문자 패턴에서는 데이터 구조와 처리를 분리한다. 데이터 구조 안을 돌아다니는 주체인 방문자를 나타내는 클래스를 준비해서 처리를 맡긴다. 새로운 처리를 추가하고 싶을 땐 새로운 방문자를 만들고 데이터 구조는 방문자를 받아들이면 된다.
방문자 패턴은 개방-폐쇠 원칙을 적용하는 방법 중 한 가지이다.
[확장에 대해 열려있다.]
클래스를 설계할 때, 특별한 이유가 없는 한 확장을 금지해서는 안된다.
[수정에 대해 닫혀있다.]
확장을 할 때 마다 기존의 클래스를 수정하면 안된다.
[Visitor]
데이터 구조 내 각각의 구체적인 요소에 visit메소드를 선언한다.
[ConcreteVisitor]
Visitor 인터페이스를 구현하고, ConcreteAccepter 역할을 처리한다.
[Acceptor]
Acceptor는 Visitor역할이 방문할 곳을 나타낸은 역할을 하고 있다. 방문자를 받아들이는 accept메소드를 선언한다.
[ConcreteAcceptor]
Acceptor 인터페이스를 구현하는 역할을 한다.
[ObjectStructure]
Acceptor역할의 집할을 취급하는 역할을 한다.
위 개념을 토대로 방문자 패턴을 적용하여 마트에서 물건을 사는 사람을 예로 들어보자!
// Element.java
public interface Element {
public void accept(Visitor visitor);
}
Element는 인터페이스로, 방문자를 받아들이기 위한 accept메소드를 가지고 있다.
// Cart.java
public class Cart implements Element {
ArrayList<Element> cart = new ArrayList<>();
public Cart() {
cart.add(new Snack());
cart.add(new Milk());
}
@Override
public void accept(Visitor visitor) {
System.out.println("카트가 준비되었습니다.");
visitor.visit(this);
for(Element element : cart) {
element.accept(visitor);
}
}
}
Cart생성자에서, 담을 물품을 추가한다. 카트에 추가된 물품의 개수 만큼 반복문을 돌며 해당 요소의 accept메소드를 실행한다.
// Snack.java
public class Snack implements Element {
@Override
public void accept(Visitor visitor) {
System.out.println("과자가 준비되었습니다.");
visitor.visit(this);
}
}
// Milk.java
public class Milk implements Element {
@Override
public void accept(Visitor visitor) {
System.out.println("우유가 준비되었습니다.");
visitor.visit(this);
}
}
// Visitor.java
public interface Visitor {
public void visit(Cart cart);
public void visit(Snack snack);
public void visit(Milk milk);
}
세개의 동일한 visit메소드를 정의한다. 각 메소드는 전달되는 인자를 통해 다른 방식으로 구현한다.
// Shopper.java
public class Shopper implements Visitor {
@Override
public void visit(Cart cart) {
System.out.println("카트를 사용합니다.");
}
@Override
public void visit(Snack snack) {
System.out.println("과자를 카트에 넣습니다.");
}
@Override
public void visit(Milk milk) {
System.out.println("우유를 카트에 넣습니다.");
}
}
Visitor인터페이스를 구현한 클래스로, 오버로딩을 통해 매개변수에 따라 다른 호출문을 출력하는 것을 확인할 수 있다.
// Client.javva
public class Client {
public static void main(String args[]) {
Shopper shopper = new Shopper();
Cart cart = new Cart();
cart.accept(shopper);
}
}
위 코드에서 보았듯이, 카트 객체를 생성할 때 과자와 우유 객체를 생성하여 카트에 추가하고 반복문을 돌며 사용자가 정의한 모든 요소에 방문하는 것을 확인할 수 있다.