# 방문자 (Visitor) 패턴
# 방문자 패턴이란 무엇인가요?
정의
기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법
- SRP를 준수하고자, 더는 책임을 늘리고 싶지 않을 때
- 버그가 발생 우려로 인하여, 수정을 더 이상 진행하고 싶지 않을 때
- 반복해서 비슷한 코드가 작성되는 상황이 발생할 수 있을 거 같을 때
중요!
더블 디스패치 (Double Dispatch)를 활용할 수 있다
- 어느 엔리먼트의 accept 메소드 인지 파악하기 위한 디스패치 한 번
- 비지터의 어느 visit 메소드인지 파악하기 위한 디스패치 한 번
# 비지터 패턴은 언제 적용되어야 할까요?
상황가정
다양한 도형을 다양한 디바이스에서 그리는 상황.
이때 도형들은 디바이스마다 크기가 다르게 출력되어야 한다.
public interface Galaxy {}
public class S22 implements Galaxy {}
public class Watch implements Galaxy {}
- 도형들이 그려질 디바이스이며, 상위 인터페이스인
Galaxy
, 하위 구현제S22
와Watch
이다.
public interface Shape {
void printTo(Galaxy device);
}
public class Triangle implements Shape {
@Override
public void printTo(Galaxy device) {
if (device instanceof S22) {
System.out.println("print Triangle to S22");
} else if (device instanceof Watch) {
System.out.println("print Triangle to Watch");
}
}
}
public class Circle implements Shape {
@Override
public void printTo(Galaxy device) {
if (device instanceof S22) {
System.out.println("print Circle to S22");
} else if (device instanceof Watch) {
System.out.println("print Circle to watch");
}
}
}
public class Rectangle implements Shape {
@Override
public void printTo(Galaxy device) {
if (device instanceof S22) {
System.out.println("print Rectangle to S22");
} else if (device instanceof Watch) {
System.out.println("print Rectangle to watch");
}
}
}
- 도형은 상위 인터페이스인 Shape 가 존재하고, 하위 구현체
Rectangle
,Circle
,Triangle
는 각 디바이스에 따른 메소드를 구현한다. - 기기마다 다른 기능 수행으로 인해 위와 같은 분기가 발생한다.
DANGER
출력하는 게 왜 도형의 역할인가?
그리고 디바이스가 늘어난다면 코드는 어떻게 될까?
새로운 도형이 생기면 또 디바이스마다 처리해줘야 하겠지?
# 비지터 패턴 적용하기
public interface Galaxy {
void print(Circle circle);
void print(Rectangle rectangle);
void print(Triangle triangle);
}
- Shape의 그림을 그리는 로직을 모두 device로 옮기고, Shape에는 accept 메서드 하나만 만들어준다.
- 각 도형들을 그리는 기능은 오버로딩을 이용하여, 각 도형에 대한 모든 메소드들을 가지도록 한다.
public class S22 implements Galaxy {
@Override
public void print(Circle circle) {
System.out.println("Print Circle to S22");
}
@Override
public void print(Rectangle rectangle) {
System.out.println("Print Rectangle to S22");
}
@Override
public void print(Triangle triangle) {
System.out.println("Print Triangle to S22");
}
}
public class Watch implements Galaxy {
@Override
public void print(Circle circle) {
System.out.println("Print Circle to Watch");
}
@Override
public void print(Rectangle rectangle) {
System.out.println("Print Rectangle to Watch");
}
@Override
public void print(Triangle triangle) {
System.out.println("Print Triangle to Watch");
}
}
public interface Shape {
void accept(Galaxy device);
}
- 각 Shape 구현체들은 Galaxy로 옮겨진 print메서드를 호출한다.
public class Circle implements Shape {
@Override
public void accept(Galaxy device) {
device.print(this);
}
}
public class Rectangle implements Shape {
@Override
public void accept(Galaxy device) {
device.print(this);
}
}
public class Triangle implements Shape {
@Override
public void accept(Galaxy device) {
device.print(this);
}
}
더블디스패치
어느 도형의 accept
인지 디스패치하고, 어떤 디바이스의 print
인지 디스패치함으로써 두번 디스패치가 발생한다.
# 방문자 패턴의 장점과 단점
장점
- 기존 코드를 변경하지 않고 새로운 코드를 추가할 수 있다.
- 추가 기능을 한 곳에 모아둘 수 있다.
단점
- 구조가 복잡하고, 새로운 Element를 추가하거나 제거할 때 모든 Visitor 코드를 변경해야 한다.
# 방문자 패턴의 예시
자바
- FileVisitor, SimpleFileVisitor
- AnnotationValueVisitor
- ElementVisitor
스프링
- BeanDefinitionVisitor
빈정의를 읽어들일때 사용하는 거라 우리가 쓸 일은 없다.
# 출처
- https://kingchan223.tistory.com/332