Visitor 패턴은 "방문자"라는 의미로 데이터 구조와 처리를 분리합니다.
즉, 데이터 구조 안을 돌아다니면서 처리할 ‘방문자’를 나타내는 클래스가 있습니다.
그리고 새로운 처리를 추가하고 싶을 때는 새로운 방문자를 만들면 됩니다.
데이터 구조 쪽에서는 방문자를 받아들일 수 있습니다.
여기에서 가장 주목해야 할 점은 Visitor와 Element가 연결되어 있는 구조가 아닌 끊어있는 구조라는 점입니다.
따라서 Visitor 패턴은 ConcreteElementA 클래스나 ConcreteElementB 클래스의 부품으로서의 독립성을 높여줄 수 있습니다.
만약에 Visitor 패턴을 사용하지 않고 ConcreteElementA 클래스에 처리할 내용을 작성하는 경우 새로운 처리를 추가하고 싶을 때마다 ConcreteElementA 클래스의 내용을 수정해야 합니다.
아래의 코드는 visitor 패턴을 사용하지 않고 accept 메소드에 처리할 내용을 작성하는 경우입니다.
이때, 처리할 내용은 간단하게 출력하는 함수로 대체해주겠습니다.
public class ConcreteElementA extends Element {
public ConcreteElementA() {}
@Override
public void accept() {
System.out.println("hello");
}
}
public class ConcreteElementA extends Element {
public ConcreteElementA() {}
@Override
public void accept() {
System.out.println("hello");
System.out.println("hello2");
System.out.println("hello3");
}
}
하지만 Visitor 패턴을 사용하게 된다면 아래의 코드와 같이 나타낼 수 있습니다.
public class ConcreteElementA extends Element {
public ConcreteElementA() {}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
public class ConcreteVisitor extends Visitor {
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("hello");
}
}
public class ConcreteElementA extends Element {
public ConcreteElementA() {}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
public class ConcreteVisitor extends Visitor {
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("hello");
System.out.println("hello2");
System.out.println("hello3");
}
}
이처럼 처리를 데이터 구조와 분리할 수 있기 때문에, 데이터 구조는 수정하지 않고 처리를 담당하는 visit 클래스만 수정하면 되기 때문에 역할 분리가 명확하여 유지 보수가 용이해질 수 있습니다.
OCP는 개방 폐쇄 원칙으로 확장에 대해서는 열려있고, 수정에 대해서는 닫혀있는다는 것을 의미합니다.
즉, Visitor 패턴을 사용하면 OCP를 지킬 수 있게 됩니다.
PART 1에서도 살펴본 예시로도 처리의 내용을 추가하는 확장에 대해서도 ConcreteElementA의 입장에서는 아무런 수정이 없었지만 처리의 내용이 추가되어서 확장에 대해서는 열려 있다는 것을 살펴보았습니다!
하지만, OCP는 Element에만 해당하는 이야기입니다.
PART 1에서 Visitor 패턴을 사용하여 처리의 내용을 추가하는 과정에서 ConcreteElementA는 수정하지 않았지만, ConcreteVisitor는 확장을 하기 위해서 수정을 했기 때문입니다.
더 나아가서 Visitor는 Element 인터페이스를 구현하는 클래스(ConcreteElementA, ConcreteElementB)가 늘어날 수록 아래와 같이 Visitor 인터페이스와 ConcreteVisitor 클래스의 메소드가 늘어나게 됩니다.
public class ConcreteElementA extends Element {
public ConcreteElementA() {}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
public class ConcreteElementB extends Element {
public ConcreteElementB() {}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
public class ConcreteVisitor extends Visitor {
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("hello");
System.out.println("hello2");
System.out.println("hello3");
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("Bye");
System.out.println("Bye2");
System.out.println("Bye3");
}
}
즉, Visitor는 모든 Element를 알고 있어야 합니다.
그리고 모든 Element의 상세 내용을 정의해야 합니다.
그러므로 Visitor 패턴은 Element의 관점에서는 OCP를 지킨다고 할 수 있지만, Visitor의 관점에서는 OCP를 지키지 않는다고 이야기할 수 있습니다.
따라서 이러한 장단점을 명확하게 파악하여 패턴을 더 적합한 상황에 적용해야함을 알 수 있습니다!!