From d1fe2180589302192eb35e4dfafc0c09ce0c6a6b Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Mon, 14 Mar 2022 19:16:52 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Add=20:=20=EB=B9=84=EC=A7=80=ED=84=B0=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 "\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/code.md" diff --git "a/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/code.md" "b/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/code.md" new file mode 100644 index 0000000..0339f20 --- /dev/null +++ "b/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/code.md" @@ -0,0 +1,234 @@ +## 코드 +### 요구사항 +- 사람인 상품 `TOP`, `FOCUS` 이 존재한다. +- 각 상품은 지역/직종 페이지의 각 상품 영역에 노출된다. +- 노출되는 곳은 `PC 사람인 웹페이지 지역/직종`, `Mobile 사람인앱 지역/직종` 이 존재하며 디바이스 별로 상품 노출 로직이 상이하다. + +### 적용전 코드 +이제 방문자 패턴을 적용하기전 일반적으로 코드를 작성해보자. +```java +//디바이스 Inteface +public interface Device {} + +//Pc일경우의 공통 로직 정의 가능 +public class Pc implements Device{} + +//모바일 일경우의 공통 로직 정의 가능 +public class Mobile implements Device{} + + +//상품 Interface +public interface Product { + void applyProduct(Device device); //상품 적용 메소드 +} + +//Focus 상품 +public class FocusProduct implements Product{ + @Override + public void applyProduct(Device device) { + if(device instanceof Mobile) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Pc) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } + } +} + +//Top 상품 +public class TopProduct implements Product { + @Override + public void applyProduct(Device device) { + if(device instanceof Mobile) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Pc) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } + } +} + +public class Client { + public static void main(String[] args) { + //상품 생성 + Product top = new TopProduct(); + Product focus = new FocusProduct(); + + //PC의 경우 + Device computer = new Pc(); + top.applyProduct(computer); + focus.applyProduct(computer); + System.out.println("============="); + + //모바일의 경우 + Device phone = new Mobile(); + top.applyProduct(phone); + focus.applyProduct(phone); + } +} + +결과 : +사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다. +사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다. +============= +사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +``` + +위 코드를 보면 `Product`의 `applyProduct()`를 통해 상품이 적용되고 있는 것을 확인할 수 있다. +그리고 각 상품의 `applyProduct()`에서 `Device`의 인스턴스를 분기로 처리하는 것을 볼 수 있다. + +이제 이 코드의 문제점은 무엇일까? + +만약 요구사항에 패드용 사람인앱이 생겨 패드 전용 지역/직종 페이지가 추가됐다고 해보자. +그럼 코드는 다음처럼 바뀔 것이다. + +```java +//패드 클래스 추가 +public class Pad implements Device {} + +public class TopProduct implements Product { + @Override + public void applyProduct(Device device) { + if(device instanceof Phone) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Computer) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Pad) { //패드 분기 추가 + System.out.println("패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + } +} + +public class FocusProduct implements Product{ + @Override + public void applyProduct(Device device) { + if(device instanceof Phone) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Computer) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Pad) { //패드 분기 추가 + System.out.println("패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } + } +} +``` + +위 코드를 `Pad`하나 추가한 것만으로도 `Product`를 상속한 모든 상품의 기존 코드가 변경된 것을 확인할 수 있으며 이는 `OCP(Open-Clossed Principle)`에 위배되는 것이다. 만약 상품 개수가 더 늘어난다면 수정이 더욱더 쉽지 않을 것이다. + +이제 이것을 방문자 패턴을 이용해 개선해보자. + +### 적용후 코드 + +```java +//Element +public interface Product { + void accept(Device device); +} + +//Concrete Element +//Top 상품 +public class TopProduct implements Product { + @Override + public void accept(Device device) { + device.applyProduct(this); + } +} + +//Concrete Element +//Focus 상품 +public class FocusProduct implements Product{ + @Override + public void accept(Device device) { + device.applyProduct(this); + } +} + +//Visitor +public interface Device { + void applyProduct(TopProduct topProduct); + void applyProduct(FocusProduct focusProduct); +} + +//Concrete Visitor +public class Pc implements Device { + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +//Concrete Visitor +public class Mobile implements Device { + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +//Concrete Visitor +public class Pad implements Device{ + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +public class Client { + public static void main(String[] args) { + Product top = new TopProduct(); + Product focus = new FocusProduct(); + + Device computer = new Pc(); + top.accept(computer); + focus.accept(computer); + System.out.println("============="); + + Device phone = new Mobile(); + top.accept(phone); + focus.accept(phone); + System.out.println("============="); + + Device pad = new Pad(); + top.accept(pad); + focus.accept(pad); + } +} + +결과 : +사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다. +사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다. +============= +사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +============= +패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +``` + + +위 코드를 보면 `Element(Product)`에는 `Visitor(Device)`를 인자로 받는 `accept()`가 정의되어있다. + +그리고 각 상품에서 오버라이딩한 것을 보면 `Device`의`applyProduct()`를 호출하면서 `this(Product Instance)`인자를 보내게 된다. + +`Visitor`에는 `Device`를 상속한 클래스 인자로 오버로딩한 메소드가 정의됨으로써 각 `Device instance`별로 처리되는 구조가 된다. + +프로세스를 간단히하면 다음과 같다. + +> `Main` → `Element.accept(Visitor)` → `Visitor.applyProduct(Element)` → `Element Instance에 따른 오버로딩 처리` +> + +이렇게 코드를 짤 경우 이제 만약 다른 `Device`가 추가된다 하더라도 기존의 코드는 전혀 변경되지 않고 `Device`를 상속한 클래스 하나만 정의해주면 된다. 즉 OCP를 만족하게 된다. From 3ff7610ca5d0818fb125fab2031d1b280a2533d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Wed, 23 Mar 2022 19:59:15 +0900 Subject: [PATCH 2/2] =?UTF-8?q?add)=20=EB=B9=84=EC=A7=80=ED=84=B0=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\355\204\260 \355\214\250\355\204\264.md" | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 "\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/\353\271\204\354\247\200\355\204\260 \355\214\250\355\204\264.md" diff --git "a/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/\353\271\204\354\247\200\355\204\260 \355\214\250\355\204\264.md" "b/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/\353\271\204\354\247\200\355\204\260 \355\214\250\355\204\264.md" new file mode 100644 index 0000000..8f3d0bb --- /dev/null +++ "b/\355\226\211\353\217\231/12\354\243\274\354\260\250-\353\271\204\354\247\200\355\204\260/summary/\353\271\204\354\247\200\355\204\260 \355\214\250\355\204\264.md" @@ -0,0 +1,277 @@ +# 비지터 패턴 + +> 기존 코드를 변경하지 않고 새로운 기능을 추가하는 방법. + +![](https://refactoring.guru/images/patterns/diagrams/visitor/structure-en-indexed.png) + +**Visitor**: `Concrete Element`를 받아서 사용하는 메소드를 구현하는 **인터페이스** + +**Concrete Visitor**: `Concrete Element` 클래스에 맞게 여러버전의 동일한 메소드를 구현한 클래스 + +**Element**: `Visitor`를 받아주는(Accept) 메소드를 선언하는 **인터페이스** + +**Concrete Element**: 이 Accept에 대해서 구현하고, 현 클래스에 대응하는 적절한 `Visitor`메소드로 작동하게 하는 클래스 + +## 비지터 패턴은 언제 사용되는가? + +- 데이터와 이러한 데이터 처리와 관련한 로직을 분리해야할 경우 + +- 데이터 구조보다는 알고리즘이 더 자주 변하는 경우 + +## 비지터 패턴 예제 코드 + +### 요구사항 + +- 사람인 상품 `TOP`, `FOCUS` 이 존재한다. +- 각 상품은 지역/직종 페이지의 각 상품 영역에 노출된다. +- 노출되는 곳은 `PC 사람인 웹페이지 지역/직종`, `Mobile 사람인앱 지역/직종` 이 존재하며 디바이스 별로 상품 노출 로직이 상이하다. + +### 적용전 코드 + +이제 방문자 패턴을 적용하기전 일반적으로 코드를 작성해보자. + +```java +//디바이스 Inteface +public interface Device {} + +//Pc일경우의 공통 로직 정의 가능 +public class Pc implements Device{} + +//모바일 일경우의 공통 로직 정의 가능 +public class Mobile implements Device{} + + +//상품 Interface +public interface Product { + void applyProduct(Device device); //상품 적용 메소드 +} + +//Focus 상품 +public class FocusProduct implements Product{ + @Override + public void applyProduct(Device device) { + if(device instanceof Mobile) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Pc) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } + } +} + +//Top 상품 +public class TopProduct implements Product { + @Override + public void applyProduct(Device device) { + if(device instanceof Mobile) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Pc) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } + } +} + +public class Client { + public static void main(String[] args) { + //상품 생성 + Product top = new TopProduct(); + Product focus = new FocusProduct(); + + //PC의 경우 + Device computer = new Pc(); + top.applyProduct(computer); + focus.applyProduct(computer); + System.out.println("============="); + + //모바일의 경우 + Device phone = new Mobile(); + top.applyProduct(phone); + focus.applyProduct(phone); + } +} + +결과 : +사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다. +사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다. +============= +사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +``` + +위 코드를 보면 `Product`의 `applyProduct()`를 통해 상품이 적용되고 있는 것을 확인할 수 있다. +그리고 각 상품의 `applyProduct()`에서 `Device`의 인스턴스를 분기로 처리하는 것을 볼 수 있다. + +이제 이 코드의 문제점은 무엇일까? + +만약 요구사항에 패드용 사람인앱이 생겨 패드 전용 지역/직종 페이지가 추가됐다고 해보자. +그럼 코드는 다음처럼 바뀔 것이다. + +```java +//패드 클래스 추가 +public class Pad implements Device {} + +public class TopProduct implements Product { + @Override + public void applyProduct(Device device) { + if(device instanceof Phone) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Computer) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } else if (device instanceof Pad) { //패드 분기 추가 + System.out.println("패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + } +} + +public class FocusProduct implements Product{ + @Override + public void applyProduct(Device device) { + if(device instanceof Phone) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Computer) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } else if (device instanceof Pad) { //패드 분기 추가 + System.out.println("패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } + } +} +``` + +위 코드를 `Pad`하나 추가한 것만으로도 `Product`를 상속한 모든 상품의 기존 코드가 변경된 것을 확인할 수 있으며 이는 `OCP(Open-Clossed Principle)`에 위배되는 것이다. 만약 상품 개수가 더 늘어난다면 수정이 더욱더 쉽지 않을 것이다. + +이제 이것을 방문자 패턴을 이용해 개선해보자. + +### 적용후 코드 + +```java +//Element +public interface Product { + void accept(Device device); +} + +//Concrete Element +//Top 상품 +public class TopProduct implements Product { + @Override + public void accept(Device device) { + device.applyProduct(this); + } +} + +//Concrete Element +//Focus 상품 +public class FocusProduct implements Product{ + @Override + public void accept(Device device) { + device.applyProduct(this); + } +} + +//Visitor +public interface Device { + void applyProduct(TopProduct topProduct); + void applyProduct(FocusProduct focusProduct); +} + +//Concrete Visitor +public class Pc implements Device { + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +//Concrete Visitor +public class Mobile implements Device { + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +//Concrete Visitor +public class Pad implements Device{ + @Override + public void applyProduct(TopProduct topProduct) { + System.out.println("패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다."); + } + + @Override + public void applyProduct(FocusProduct focusProduct) { + System.out.println("패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다."); + } +} + +public class Client { + public static void main(String[] args) { + Product top = new TopProduct(); + Product focus = new FocusProduct(); + + Device computer = new Pc(); + top.accept(computer); + focus.accept(computer); + System.out.println("============="); + + Device phone = new Mobile(); + top.accept(phone); + focus.accept(phone); + System.out.println("============="); + + Device pad = new Pad(); + top.accept(pad); + focus.accept(pad); + } +} + +결과 : +사람인 웹페이지 지역/직종 TOP 영역에 공고를 노출한다. +사람인 웹페이지 지역/직종 Focus 영역에 공고를 노출한다. +============= +사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +============= +패드용 사람인앱 지역/직종 TOP 영역에 공고를 노출한다. +패드용 사람인앱 지역/직종 Focus 영역에 공고를 노출한다. +``` + +위 코드를 보면 `Element(Product)`에는 `Visitor(Device)`를 인자로 받는 `accept()`가 정의되어있다. + +그리고 각 상품에서 오버라이딩한 것을 보면 `Device`의`applyProduct()`를 호출하면서 `this(Product Instance)`인자를 보내게 된다. + +`Visitor`에는 `Device`를 상속한 클래스 인자로 오버로딩한 메소드가 정의됨으로써 각 `Device instance`별로 처리되는 구조가 된다. + +프로세스를 간단히하면 다음과 같다. + +> `Main` → `Element.accept(Visitor)` → `Visitor.applyProduct(Element)` → `Element Instance에 따른 오버로딩 처리` + +이렇게 코드를 짤 경우 이제 만약 다른 `Device`가 추가된다 하더라도 기존의 코드는 전혀 변경되지 않고 `Device`를 상속한 클래스 하나만 정의해주면 된다. 즉 OCP를 만족하게 된다. + +## 패턴의 장/단점 + +✅ 장점: + +- OCP(개방/폐쇄원칙) 클래스 변경없이도 다른 클래스들의 객체를 실행시키는 새로운 행동을 추가 할 수 있다. +- SRP(단일 책임 원칙) 동일 행동을 하는 여러 버전 메소들이 같은 클래스쪽으로 몰아져 있을 수 있다. +- 비지터 객체는 각각의 객체들과 함께 작동하는 동안 몇몇 유용한 정보들을 모을 수 있다. 객체 트리, 이런 구조와 같이 복잡한 객체 구조들을 방문하면서, 이런 객체들에 방문자 적용할때 편하다. + +🚨 단점: + +- 클래스가 elements 계층에 추가되거나, elements 계층에서 빠지게 될때, 모든 visitor들을 수정해야 한다. +- Visitor들은 사용 가능한 elements에 private 변수나, 메소드들에 필수적인 접근을 해야하면 어려울 수 있다. + +## 비슷한 패턴 + +- **비지터 패턴**은 **커맨드 패턴**의 강력한 버전이라고 봐도 좋다. 서로 다른 클래스들의 다양한 객체에 대해서 실행시킬 수 있기 때문이다. + +- 전체 **컴포짓 트리**를 **비지터 패턴**을 통해서 실행 시킬 수 있다. + +- **비지터**와 **이터레이터**를 함께 쓰면 복잡한 데이터 구조를 순회하고, 모든 `elements`가 서로 다른 클래스를 가지고 있더라도 일부 명령을 실행 시킬 수 있다.