(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 방문자 패턴(Visitor Pattern)
비지터 패턴은, 기존 코드를 건드리지 않고 새로운 코드를 추가하는 방법을 제안하는 패턴
- 방문자 패턴은 비지니스 로직을 객체 구조에서 분리시키는 디자인 패턴입니다.
- 비슷한 종류의 객체들을 가진 그룹에서 작업을 수행해야 할 때 주로 사용됩니다.
- 📌 더블 디스패치 (Doublie Dispatch)를 활용합니다. https://www.baeldung.com/ddd-double-dispatch
- 디스패치란 (Dispatch 1.(특별한 목적을 위해)보내다 2.(메세지를) 보내다 )
- 자바는 객체지향 프로그래밍 언어로써 객체들 간의 메세지 전송을 기반으로 문제를 해결합니다.
- 메세지 전송이라는 표현은 결국 메서드를 호출하는 것인데 그것을 Dispatch라고 합니다.
- 자바는 하위타입으로의 묵시적 형변환을 지원하지 않는, 싱글 디스패치 (Single Dispatch) 언어입니다. - 따라서 런타임시에 부모 객체의 구현체로 어떤 자식 클래스가 들어오는지 확인하여 서로 다른 매서드를 호출해주는 동적 디스패치를 지원하지 않습니다.
- 이러한 언어의 한계적 특성으로 인해 인자에 동적 디스패치를 활용하지못하기때문에, 이를 위해 한번 더 다형성을 이용함으로서 호출객체 동적 디스패치를 이용하는 기법이 더블 디스패치 입니다.
- 디스패치란 (Dispatch 1.(특별한 목적을 위해)보내다 2.(메세지를) 보내다 )
- Client : 명령을 보냅니다.
- Visitor : 명령을 수행하기 위해 필요한 메소드를 정의하는 인터페이스입니다.
- ConcreteVisitor : 명령을 수행하는 메소드를 구현합니다.
- Element : Visit를 사용할 수 있는지 확인하는 accept 메소드를 정의하는 인터페이스입니다.
- ConcreteElement : Visitable에서 정의된 accept 메소드를 구현하며 Visitor객체는 이 객체를 통하여 명령이 전달됩니다.
✔️ 방문자 패턴 예시
- 인터페이스(2)를 인자로 받는 인터페이스(1)가 있다고 가정하자
- 그후, 1차 인터페이스(1)의 구현체에서 인자로 받은 2차 인터페이스 (2) 의 구현체 타입에따라 다른 행위를 해야하는 요구조건이라면
- 비지터 패턴을 적용하지 않고 "분기 처리"로 해당 매개변수로 들어온 구현체의 클래스를 판단한다면 아래와 같은 코드가 생성됩니다.
방문자 패턴 적용 전
- Shape 인터페이스를 상속받는 "도형" 구현체에게 들어오는 Device 인터페이스의 구현체에따라 각각 모든 조건에 따른 분기처리를 진행해야합니다.
- 이렇게하면, Shape 혹은 Device의 구현체가 늘어나거나 특정 상황에서의 행위가 달라지면 무수히 많은 코드들을 수정해야하는 매우 강한 결합상태입니다.
- 방문자 패턴의 장점은 Double Dispatcher 이므로, 굳이 표현한다면 Shape 구현체로 이동되는 1차 Dispatch 가 일어납니다.
public interface Shape {
void printTo(Device device);
}
//구현체 1
public class Rectangle implements Shape {
//디바이스의 구현체가 추가되더라도 여기 코드가 변경됨
@Override
public void printTo(Device device) {
if (device instanceof Phone) {
System.out.println("print Rectangle to Phone");
}
if (device instanceof Watch) {
System.out.println("print Rectangle to Watch");
}
}
}
//구현체 2
public class Triangle implements Shape{
@Override
public void printTo(Device device) {
if (device instanceof Phone) {
System.out.println("print Triangle to Phone");
}
if (device instanceof Watch) {
System.out.println("print Triangle to Watch");
}
}
}
public interface Device {
}
public class Phone implements Device{
}
public class Watch implements Device{
}
public class Client {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
Shape triangle = new Triangle();
Device phone = new Phone();
Device watch = new Watch();
rectangle.printTo(watch);
rectangle.printTo(phone);
triangle.printTo(watch);
triangle.printTo(phone);
}
}
👇
방문자 패턴 적용 후
- 이제 비지터 패턴을 이용하여 해당 코드의 의존성을 제거해 보도록 하겠습니다.
- 방문자 패턴의 핵심은 더블 디스패치. 기존의 Shape 구현체에서 일어나던 비지니스 로직을 Device 에게 직접 처리하도록 수정한것입니다.
↓
- Client에서 accept() 메소드를 찾기위해 1차 Dispatch (정적 디스패치) 가 일어나고
- print() 객체를 호출하기 위해 위해 메서드를 호출하는 인자가 구현체가 없는 인터페이스 타입인 경우 런타임시에 객체를 찾아 알맞는 메서드를 호출하는 2차 Dispatch (동적 디스패치) 가 일어납니다.
- (정적 1 + 동적 1 = 2번의 디스패치가 일어남)
public interface Shape {
//비지터로 디스페치 하기위한 기능
void accept(Device device);
}
public class Triangle implements Shape {
@Override
public void accept(Device device) {
device.print(this);
}
}
public class Circle implements Shape {
@Override
public void accept(Device device) {
device.print(this);
}
}
public class Rectangle implements Shape {
@Override
public void accept(Device device) {
device.print(this);
}
}
public interface Device {
void print(Circle circle);
void print(Triangle triangle);
void print(Rectangle rectangle);
}
//필요한 기능이 생기면 변경하지 않고 추가하면 됨
public class Watch implements Device {
@Override
public void print(Circle circle) {
System.out.println("print Circle to Watch");
}
@Override
public void print(Triangle triangle) {
System.out.println("print Triangle to Watch");
}
@Override
public void print(Rectangle rectangle) { System.out.println("print Rectangle to Watch");}
}
public class Phone implements Device {
@Override
public void print(Circle circle) {
System.out.println("print Circle to Phone");
}
@Override
public void print(Triangle triangle) {
System.out.println("print Triangle to Phone");
}
@Override
public void print(Rectangle rectangle) {
System.out.println("print Rectangle to Phone");
}
}
호출하는 클라이언트는 똑같습니다.
public class Client {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
Shape circle = new Circle();
Shape triangle = new Triangle();
Device phone = new Phone();
Device watch = new Watch();
//상위 타입인 device 가 지원이 되지 않음
rectangle.accept(phone);
rectangle.accept(watch);
circle.accept(phone);
circle.accept(watch);
triangle.accept(phone);
triangle.accept(watch);
}
}
✔️ 방문자 패턴 장점, 단점
• 장점
- 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있다. (1차 디스패치기능은 추가해야함)
- 추가 기능을 한 곳에 모아둘 수 있다.
• 단점
- 새로운 Element 를 추가하거나 제거할 때 모든 Visitor 코드를 변경해야 한다.
- visit 메소드의 return 타입을 각각 파악하고 있어야 한다.
참고
- 더블 디스패처 : https://multifrontgarden.tistory.com/133
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 - 템플릿 메소드 패턴(Template Method Pattern) (0) | 2023.04.24 |
---|---|
[디자인 패턴] 행동 패턴 - 전략 패턴 (Strategy Pattern) (0) | 2023.04.21 |
[디자인 패턴] 행동 패턴 - 상태 패턴 (State Pattern) (0) | 2023.04.19 |
[디자인 패턴] 행동 패턴 - 옵저버(관찰자) 패턴 (Observer Pattern) (0) | 2023.04.13 |
[디자인 패턴] 행동 패턴 - 메멘토 패턴 (Memento Pattern) (0) | 2023.04.12 |