From 3442d55ac61c5c5ed84dfba4d7e3964bdde0f5a9 Mon Sep 17 00:00:00 2001 From: CJW23 Date: Mon, 7 Mar 2022 01:31:43 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EC=97=B0=EC=87=84=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20-=20=EC=98=88=EC=A0=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/example-code.md" | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 "\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/example-code.md" diff --git "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/example-code.md" "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/example-code.md" new file mode 100644 index 0000000..c6c228b --- /dev/null +++ "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/example-code.md" @@ -0,0 +1,350 @@ +# 책임 연쇄 패턴 예제 + +먼저 개발자가 개발 업무를 처리하고 작업자를 저장하는 과정이 있다 해보자. 아래는 간단 예제 코드이다. + +```java +/** + * 업무처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + //개발 작업자 + private String developer; + + public DefaultWorkHandler(String developer) { + this.developer = developer; + } + + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new DefaultWorkHandler("최재우"); + //업무 처리자 저장 클래스 + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +결과 +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +=================== +``` + +위의 코드는 간단하게 `DefaultWorkHandler.handle()`을 통해 개발자가 업무를 처리하는 기능을 구현했다. + +그렇다면 이번엔 `개발 업무`를 진행하기전에 `기획 작업`이 추가돼야한다 가정하자. + +아래 예제는 기존 코드에 기획 작업을 추가하는 2가지의 방법이다. + +```java +1. 기존 handle()에 기획 업무까지 처리하는 코드 + +/** + * 업무처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + private String planner; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.printf("기획 : %s\n", this.planner); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + private String developer; + private String planner; + + public DefaultWorkHandler(String developer, String planner) { + this.developer = developer; + this.planner = planner; + } + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + //기획 업무를 기존 개발 업무처리 메소드에 같이 처리 + System.out.printf("%s 기획자가 업무를 처리했습니다\n", planner); + //기획자 등록 + ideaRequest.setPlanner(planner); + + //개발 업무 처리 + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + //개발자 등록 + ideaRequest.setDeveloper(this.developer); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new DefaultWorkHandler("최재우", "박영준"); + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +결과 +박영준 기획자가 업무를 처리했습니다 +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +기획 : 박영준 +=================== + +2. 기획 업무를 처리할 DefaultWOrkHandler를 상속한 Handler 클래스 생성 + +public class IdeaRequest { + //IdeaRequest 위와 동일 +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + private String developer; + + public DefaultWorkHandler(String developer) { + this.developer = developer; + } + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + } +} + +/** + * 기획 업무 처리 클래스 + */ +public class PlaningWorkHandler extends DefaultWorkHandler{ + private String planner; + + public PlaningWorkHandler(String developer, String planner) { + //부모 클래스에 개발자 전달 + super(developer); + //기획자 이름 저장 + this.planner = planner; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + //기획 업무 처리 + System.out.printf("%s 기획자가 업무를 처리했습니다.\n", this.planner); + ideaRequest.setPlanner(this.planner); + //개발 업무 호출 + super.handle(ideaRequest); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new PlaningWorkHandler("최재우", "박영준"); + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +박영준 기획자가 업무를 처리했습니다. +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +기획 : 박영준 +=================== +``` + +1번 방법 같은 경우 기존 `handle()` 메소드에 기획 업무까지 같이 처리하게끔 구현된 것을 볼 수 있는데, 이 경우 `단일 책임 원칙(SRP: Single Responsibility Principle)`을 위반하게 된다. + +2번 방법은 `DefaultWorkHandler`를 상속 받은 `PlaningWorkHandler`을 구현함으로써 기존 코드를 수정하지 않고 기획 작업을 추가했으므로 `SRP` 는 만족을 한다. 하지만 내가 사용할 구체적인 클래스 구현체를 클라이언트에서 직접 변경해줘야한다. +예를 들어 **개발만 작업하고 싶으면** `DefaultWorkHandler`, **기획 작업을 추가하고 싶으면**`PlaningWorkHandler`를 사용해야한다는 것 즉, `커플링`이 높아지게 된다. + +또한 추후에 `개발 작업`후 `QA테스트 작업`이 추가되거나, `기획 작업`을 제외하고 `개발 작업`과 `QA테스트`만 진행해야하는 경우마다 각각의 `WorkHandler`를 새로 작성해줘야하므로 매우 복잡해진다. + +이제 `책임 연쇄 패턴`을 적용해보자. + +![Untitled](https://user-images.githubusercontent.com/32676275/156932259-757d5fbb-93be-4305-8704-03c5b1f25e9f.png) + +```java +/** + * 작업 처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + private String planner; + private String tester; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.printf("기획 : %s\n", this.planner); + System.out.printf("QA : %s\n", this.tester); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 각 작업의 공통 인터페이스 + */ +public interface WorkHandler { + void handle(IdeaRequest ideaRequest); +} + +/** + * 개발 업무 처리 Handler + */ +class DeveloperWorkHandler implements WorkHandler { + private WorkHandler workHandler; + private String developer; + + public DeveloperWorkHandler(WorkHandler workHandler, String developer) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.developer = developer; + } + + //업무 처리 메소드 + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + + if(this.workHandler != null) { + //다음 업무 처리 + this.workHandler.handle(ideaRequest); + } + } +} + +/** + * 기획 업무 처리 Handler + */ +public class PlaningWorkHandler implements WorkHandler { + private String planner; + private WorkHandler workHandler; + + public PlaningWorkHandler(WorkHandler workHandler,String planner) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.planner = planner; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 기획자가 업무를 처리했습니다.\n", this.planner); + ideaRequest.setPlanner(this.planner); + + if(this.workHandler != null) { + //다음 업무 처리 + this.workHandler.handle(ideaRequest); + } + } +} + +/** + * QA 업무 처리 Handler + */ +public class TestWorkHandler implements WorkHandler{ + private WorkHandler workHandler; + private String tester; + + public TestWorkHandler(WorkHandler workHandler, String tester) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.tester = tester; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 테스터가 업무를 처리했습니다.\n", this.tester); + ideaRequest.setTester(this.tester); + + if(this.workHandler != null) { + //다음 업무 처리 + workHandler.handle(ideaRequest); + } + } +} + +/** + * 메인 클래스 + */ +public class Main { + private WorkHandler workHandler; + + public Main(WorkHandler workHandler) { + this.workHandler = workHandler; + } + + public void doWork() { + IdeaRequest ideaRequest = new IdeaRequest(); + //체이닝으로 연결된 workHandler 작업 수행 + this.workHandler.handle(ideaRequest); + //작업자 출력 + ideaRequest.printWorker(); + } + + public static void main(String[] args) { + //체이닝으로 각 handler를 연결 + //기획 -> 개발 -> 테스트 작업 진행 + WorkHandler workHandler = + new PlaningWorkHandler( + new DeveloperWorkHandler( + new TestWorkHandler(null, "홍길동"), "아무개"), "오징어"); + + Main main = new Main(workHandler); + //작업 수행 + main.doWork(); + + //개발 -> 테스트 작업 진행 + WorkHandler workHandler1 = + new DeveloperWorkHandler( + new TestWorkHandler(null, "홍길동"), "아무개"); + + main = new Main(workHandler1); + //작업 수행 + main.doWork(); + } +} +``` + +책임 연쇄 패턴을 적용함으로써 `WorkHandler`를 상속한 각 업무(`Development`, `Planing`, `QA`)를 구현하고 각 구현체에 `WorkHandler` 객체를 생성자에서 받음으로써 체이닝이 된 것을 볼 수 있다. + +이렇게 됨으로써 `클라이언트(Main)`에서는 `WorkHandler`의 구체적인 구현체를 알지 못해도 `handle()` 만 호출함으로써 알아서 체이닝된 업무들이 실행될 것이다. 또한 **필요한 업무만 체이닝하거나 순서도 바꿔서 사용할 수 있게 된다.** \ No newline at end of file From e8275a9ce173726f232004a0ae99f555333f06e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Sun, 20 Mar 2022 23:43:03 +0900 Subject: [PATCH 2/5] =?UTF-8?q?add)=20=EC=B1=85=EC=9E=84=EC=97=B0=EC=87=84?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\354\207\204 \355\214\250\355\204\264.md" | 397 ++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 "\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" diff --git "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" new file mode 100644 index 0000000..ff617d2 --- /dev/null +++ "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" @@ -0,0 +1,397 @@ +# 책임 연쇄 패턴 + +> 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴 + +![](https://refactoring.guru/images/patterns/diagrams/chain-of-responsibility/structure-indexed.png) + +**Handler**: 요청을 수신하고 처리객체들의 집합에 전달하는 인터페이스입니다. 집합의 첫 번째 핸들러에 대한 정보만 가지고 있으며 그 이후의 핸들러에 대해서는 알지 못합니다. + +**Base Handler**: 모든 핸들러 클래스에 공통적으로 사용 가능한 코드를 입력할 수 있는 옵션 클래스입니다. (Optional) + +**Concrete handlers** : 요청을 처리하는 실제 처리객체입니다. + +## 책임 연쇄 패턴은 언제 사용되는가? + +- **결합을 느슨하게 하고 싶을때.** 요청을 보내는 객체와 처리하는 객체간의 결합도를 낮추기 위해서 사용한다. + +## 책임 연쇄 패턴 예제 코드 + +먼저 개발자가 개발 업무를 처리하고 작업자를 저장하는 과정이 있다 해보자. 아래는 간단 예제 코드이다. + +```java +/** + * 업무처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + //개발 작업자 + private String developer; + + public DefaultWorkHandler(String developer) { + this.developer = developer; + } + + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new DefaultWorkHandler("최재우"); + //업무 처리자 저장 클래스 + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +결과 +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +=================== +``` + +위의 코드는 간단하게 `DefaultWorkHandler.handle()`을 통해 개발자가 업무를 처리하는 기능을 구현했다. + +그렇다면 이번엔 `개발 업무`를 진행하기전에 `기획 작업`이 추가돼야한다 가정하자. + +아래 예제는 기존 코드에 기획 작업을 추가하는 2가지의 방법이다. + +```java +1. 기존 handle()에 기획 업무까지 처리하는 코드 + +/** + * 업무처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + private String planner; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.printf("기획 : %s\n", this.planner); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + private String developer; + private String planner; + + public DefaultWorkHandler(String developer, String planner) { + this.developer = developer; + this.planner = planner; + } + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + //기획 업무를 기존 개발 업무처리 메소드에 같이 처리 + System.out.printf("%s 기획자가 업무를 처리했습니다\n", planner); + //기획자 등록 + ideaRequest.setPlanner(planner); + + //개발 업무 처리 + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + //개발자 등록 + ideaRequest.setDeveloper(this.developer); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new DefaultWorkHandler("최재우", "박영준"); + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +결과 +박영준 기획자가 업무를 처리했습니다 +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +기획 : 박영준 +=================== + +2. 기획 업무를 처리할 DefaultWOrkHandler를 상속한 Handler 클래스 생성 + +public class IdeaRequest { + //IdeaRequest 위와 동일 +} + +/** + * 업무 처리 클래스 + */ +public class DefaultWorkHandler { + private String developer; + + public DefaultWorkHandler(String developer) { + this.developer = developer; + } + //업무 처리 메소드 + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + } +} + +/** + * 기획 업무 처리 클래스 + */ +public class PlaningWorkHandler extends DefaultWorkHandler{ + private String planner; + + public PlaningWorkHandler(String developer, String planner) { + //부모 클래스에 개발자 전달 + super(developer); + //기획자 이름 저장 + this.planner = planner; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + //기획 업무 처리 + System.out.printf("%s 기획자가 업무를 처리했습니다.\n", this.planner); + ideaRequest.setPlanner(this.planner); + //개발 업무 호출 + super.handle(ideaRequest); + } +} + +public class Main { + public static void main(String[] args) { + DefaultWorkHandler defaultWorkHandler = new PlaningWorkHandler("최재우", "박영준"); + IdeaRequest ideaRequest = new IdeaRequest(); + //업무 처리 + defaultWorkHandler.handle(ideaRequest); + //업무 처리자 출력 + ideaRequest.printWorker(); + } +} + +박영준 기획자가 업무를 처리했습니다. +최재우 개발자가 업무를 처리했습니다. +=================== +개발 : 최재우 +기획 : 박영준 +=================== +``` + +1번 방법 같은 경우 기존 `handle()` 메소드에 기획 업무까지 같이 처리하게끔 구현된 것을 볼 수 있는데, 이 경우 `단일 책임 원칙(SRP: Single Responsibility Principle)`을 위반하게 된다. + +2번 방법은 `DefaultWorkHandler`를 상속 받은 `PlaningWorkHandler`을 구현함으로써 기존 코드를 수정하지 않고 기획 작업을 추가했으므로 `SRP` 는 만족을 한다. 하지만 내가 사용할 구체적인 클래스 구현체를 클라이언트에서 직접 변경해줘야한다. +예를 들어 **개발만 작업하고 싶으면** `DefaultWorkHandler`, **기획 작업을 추가하고 싶으면**`PlaningWorkHandler`를 사용해야한다는 것 즉, `커플링`이 높아지게 된다. + +또한 추후에 `개발 작업`후 `QA테스트 작업`이 추가되거나, `기획 작업`을 제외하고 `개발 작업`과 `QA테스트`만 진행해야하는 경우마다 각각의 `WorkHandler`를 새로 작성해줘야하므로 매우 복잡해진다. + +이제 `책임 연쇄 패턴`을 적용해보자. + +![Untitled](https://user-images.githubusercontent.com/32676275/156932259-757d5fbb-93be-4305-8704-03c5b1f25e9f.png) + +```java +/** + * 작업 처리자 저장 클래스 + */ +public class IdeaRequest { + private String developer; + private String planner; + private String tester; + + public void printWorker() { + System.out.println("==================="); + System.out.printf("개발 : %s\n", this.developer); + System.out.printf("기획 : %s\n", this.planner); + System.out.printf("QA : %s\n", this.tester); + System.out.println("==================="); + } + //...Setter + //...Getter +} + +/** + * 각 작업의 공통 인터페이스 + */ +public interface WorkHandler { + void handle(IdeaRequest ideaRequest); +} + +/** + * 개발 업무 처리 Handler + */ +class DeveloperWorkHandler implements WorkHandler { + private WorkHandler workHandler; + private String developer; + + public DeveloperWorkHandler(WorkHandler workHandler, String developer) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.developer = developer; + } + + //업무 처리 메소드 + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); + ideaRequest.setDeveloper(this.developer); + + if(this.workHandler != null) { + //다음 업무 처리 + this.workHandler.handle(ideaRequest); + } + } +} + +/** + * 기획 업무 처리 Handler + */ +public class PlaningWorkHandler implements WorkHandler { + private String planner; + private WorkHandler workHandler; + + public PlaningWorkHandler(WorkHandler workHandler,String planner) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.planner = planner; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 기획자가 업무를 처리했습니다.\n", this.planner); + ideaRequest.setPlanner(this.planner); + + if(this.workHandler != null) { + //다음 업무 처리 + this.workHandler.handle(ideaRequest); + } + } +} + +/** + * QA 업무 처리 Handler + */ +public class TestWorkHandler implements WorkHandler{ + private WorkHandler workHandler; + private String tester; + + public TestWorkHandler(WorkHandler workHandler, String tester) { + //다음 업무로 체이닝할 WorkHandler 객체 + this.workHandler = workHandler; + this.tester = tester; + } + + @Override + public void handle(IdeaRequest ideaRequest) { + System.out.printf("%s 테스터가 업무를 처리했습니다.\n", this.tester); + ideaRequest.setTester(this.tester); + + if(this.workHandler != null) { + //다음 업무 처리 + workHandler.handle(ideaRequest); + } + } +} + +/** + * 메인 클래스 + */ +public class Main { + private WorkHandler workHandler; + + public Main(WorkHandler workHandler) { + this.workHandler = workHandler; + } + + public void doWork() { + IdeaRequest ideaRequest = new IdeaRequest(); + //체이닝으로 연결된 workHandler 작업 수행 + this.workHandler.handle(ideaRequest); + //작업자 출력 + ideaRequest.printWorker(); + } + + public static void main(String[] args) { + //체이닝으로 각 handler를 연결 + //기획 -> 개발 -> 테스트 작업 진행 + WorkHandler workHandler = + new PlaningWorkHandler( + new DeveloperWorkHandler( + new TestWorkHandler(null, "홍길동"), "아무개"), "오징어"); + + Main main = new Main(workHandler); + //작업 수행 + main.doWork(); + + //개발 -> 테스트 작업 진행 + WorkHandler workHandler1 = + new DeveloperWorkHandler( + new TestWorkHandler(null, "홍길동"), "아무개"); + + main = new Main(workHandler1); + //작업 수행 + main.doWork(); + } +} +``` + +책임 연쇄 패턴을 적용함으로써 `WorkHandler`를 상속한 각 업무(`Development`, `Planing`, `QA`)를 구현하고 각 구현체에 `WorkHandler` 객체를 생성자에서 받음으로써 체이닝이 된 것을 볼 수 있다. + +이렇게 됨으로써 `클라이언트(Main)`에서는 `WorkHandler`의 구체적인 구현체를 알지 못해도 `handle()` 만 호출함으로써 알아서 체이닝된 업무들이 실행될 것이다. 또한 **필요한 업무만 체이닝하거나 순서도 바꿔서 사용할 수 있게 된다.** + +## 패턴의 장/단점 + +장점: + +- 요청에 대한 처리의 순서를 컨트롤 할 수 있다. +- SRP(단일책임원칙) 작업 실행하는 클래스에서 작업을 호출하는 클래스를 분리할 수 있다. +- OCP(개방/폐쇄원칙) 존재하는 클라이언트 코드 파괴 없이 앱 안에서의 새로운 핸들러를 추가할 수 있다. + +단점: + +- 몇몇 응답은 처리되지 않을 수 있습니다. + +## 비슷한 패턴 + +- **책임연쇄패턴, 커맨드패턴, 중제자, 옵저버**는 요청의 발송자와 수신자를 접속하는 다양한 방법으로 대처한다. + - **책임연쇄패턴**은 잠재적인 리시버중 하나가 처리될때까지 요청을 동적인 리시버체인에 따라서 순차적인 처리를 한다. + + - **커맨드 패턴**은 수신자와 송신자 사이의 단방향의 연결을 설립한다. + + - **중제자 패턴**은 송신자와 수신자를 직접적인 통신은 배제하고 중제자 객체를 통한 간접적인 소통 가능하도록 강제한다. + + - **옵저버 패턴**은 수신자의 전달받은 요청으로부터 동적으로 구독하거나 비구독하는 방식으로 구성한다 +- **책임연쇄패턴**은 **컴포짓패턴**과 함께 사용되는 경우가 많다. 이 경우에는 leaf 컴포넌트에 요청을 주게될때, 모든 부모 컴포넌트의 체인을 통과해서 오브젝트 트리루트로 도달 가능하게 만들 수 있다. +- **책임 연쇄 패턴의 핸들러**는 **커맨드**로 구현 할 수 있다. 이 경우는 너는 요청(Request)으로 표현되는 동일 컨텍스트 객체에 대해서 다양한 작업들을 실행시킬 수 있다. + 하지만, 요청 자체가 커맨드 객체인 다른 접근 방식이 존재한다. 이 경우에는 체인에 연결되어있는 다른 컨택스트에서 동일한 작업을 수행할 수 있다. +- **책임 연쇄 패턴**과 **데코레이터**는 클래스 구조상 비슷한 형식을 가지고 있다 . 두 패턴은 모두 한개의 객체로 실행시키기 위해 재귀적 합성방식을 이용한다. 그러나 몇몇 부분은 확연하게 차이점이 있다. + - **책임 연쇄 핸들러**는 각각 서로가 독립적으로 임의의 조작을 실행시킬 수 있다. 또한 언제든지 이런 요청들을 멈출 수도 있다. + - **데코레이터**들은 기본 인터페이스을 기반으로 일관성을 유지하면서 개체의 동작을 확장할 수 있다. 또한 데코레이터는 요청의 흐름을 끊을 수 없다. + + From b1a0b4459ef0dec4bb21819e0c8187a3c33785e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Sun, 20 Mar 2022 23:45:01 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix)=20=EC=96=91=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...04 \354\227\260\354\207\204 \355\214\250\355\204\264.md" | 6 ++++++ 1 file changed, 6 insertions(+) diff --git "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" index ff617d2..6453927 100644 --- "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" +++ "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" @@ -388,10 +388,16 @@ public class Main { - **옵저버 패턴**은 수신자의 전달받은 요청으로부터 동적으로 구독하거나 비구독하는 방식으로 구성한다 - **책임연쇄패턴**은 **컴포짓패턴**과 함께 사용되는 경우가 많다. 이 경우에는 leaf 컴포넌트에 요청을 주게될때, 모든 부모 컴포넌트의 체인을 통과해서 오브젝트 트리루트로 도달 가능하게 만들 수 있다. + - **책임 연쇄 패턴의 핸들러**는 **커맨드**로 구현 할 수 있다. 이 경우는 너는 요청(Request)으로 표현되는 동일 컨텍스트 객체에 대해서 다양한 작업들을 실행시킬 수 있다. 하지만, 요청 자체가 커맨드 객체인 다른 접근 방식이 존재한다. 이 경우에는 체인에 연결되어있는 다른 컨택스트에서 동일한 작업을 수행할 수 있다. + + - **책임 연쇄 패턴**과 **데코레이터**는 클래스 구조상 비슷한 형식을 가지고 있다 . 두 패턴은 모두 한개의 객체로 실행시키기 위해 재귀적 합성방식을 이용한다. 그러나 몇몇 부분은 확연하게 차이점이 있다. + + - **책임 연쇄 핸들러**는 각각 서로가 독립적으로 임의의 조작을 실행시킬 수 있다. 또한 언제든지 이런 요청들을 멈출 수도 있다. + - **데코레이터**들은 기본 인터페이스을 기반으로 일관성을 유지하면서 개체의 동작을 확장할 수 있다. 또한 데코레이터는 요청의 흐름을 끊을 수 없다. From b6d655a737e609204d62ceb53462e1e5cc7992dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Mon, 21 Mar 2022 22:58:32 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix'=EC=96=B4=EC=83=89=ED=95=9C=20=EA=B5=AC?= =?UTF-8?q?=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...36\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" index 6453927..c2fc052 100644 --- "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" +++ "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" @@ -379,7 +379,7 @@ public class Main { ## 비슷한 패턴 -- **책임연쇄패턴, 커맨드패턴, 중제자, 옵저버**는 요청의 발송자와 수신자를 접속하는 다양한 방법으로 대처한다. +- **책임연쇄패턴, 커맨드패턴, 중제자, 옵저버**는 요청에 대한 발송자와 수신자 연결 방식이 다양하다 - **책임연쇄패턴**은 잠재적인 리시버중 하나가 처리될때까지 요청을 동적인 리시버체인에 따라서 순차적인 처리를 한다. - **커맨드 패턴**은 수신자와 송신자 사이의 단방향의 연결을 설립한다. From 27cb61c2c6ea8d60d6b366ef724fd33b7db1b3ef Mon Sep 17 00:00:00 2001 From: Choi Jae Woo <32676275+CJW23@users.noreply.github.com> Date: Tue, 22 Mar 2022 23:28:04 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Update=20=EC=B1=85=EC=9E=84=20=EC=97=B0?= =?UTF-8?q?=EC=87=84=20=ED=8C=A8=ED=84=B4.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 글 수정 --- ...0\354\207\204 \355\214\250\355\204\264.md" | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" index c2fc052..d2ebaa3 100644 --- "a/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" +++ "b/\355\226\211\353\217\231/7\354\243\274\354\260\250-\354\261\205\354\236\204 \354\227\260\354\207\204/summary/\354\261\205\354\236\204 \354\227\260\354\207\204 \355\214\250\355\204\264.md" @@ -4,19 +4,19 @@ ![](https://refactoring.guru/images/patterns/diagrams/chain-of-responsibility/structure-indexed.png) -**Handler**: 요청을 수신하고 처리객체들의 집합에 전달하는 인터페이스입니다. 집합의 첫 번째 핸들러에 대한 정보만 가지고 있으며 그 이후의 핸들러에 대해서는 알지 못합니다. +**Handler**: 요청을 수신하고 처리객체들의 집합에 전달하는 인터페이스, 집합의 첫 번째 핸들러 정보만 알고 이후 핸들러는 알지 못함 -**Base Handler**: 모든 핸들러 클래스에 공통적으로 사용 가능한 코드를 입력할 수 있는 옵션 클래스입니다. (Optional) +**Base Handler**: 모든 핸들러 클래스에 공통적으로 사용 가능한 코드를 입력할 수 있는 옵션 클래스 (Optional) -**Concrete handlers** : 요청을 처리하는 실제 처리객체입니다. +**Concrete handlers** : 요청을 처리하는 실제 처리객체 ## 책임 연쇄 패턴은 언제 사용되는가? -- **결합을 느슨하게 하고 싶을때.** 요청을 보내는 객체와 처리하는 객체간의 결합도를 낮추기 위해서 사용한다. +- **결합을 느슨하게 하고 싶을때.** 요청을 보내는 객체와 처리하는 객체간의 결합도를 낮추기 위해서 사용 ## 책임 연쇄 패턴 예제 코드 -먼저 개발자가 개발 업무를 처리하고 작업자를 저장하는 과정이 있다 해보자. 아래는 간단 예제 코드이다. +아래 예제는 개발자가 개발업무 처리후 작업자를 저장하는 과정을 작성한 코드이다. ```java /** @@ -39,7 +39,7 @@ public class IdeaRequest { */ public class DefaultWorkHandler { //개발 작업자 - private String developer; + private String developer; public DefaultWorkHandler(String developer) { this.developer = developer; @@ -56,7 +56,7 @@ public class Main { public static void main(String[] args) { DefaultWorkHandler defaultWorkHandler = new DefaultWorkHandler("최재우"); //업무 처리자 저장 클래스 - IdeaRequest ideaRequest = new IdeaRequest(); + IdeaRequest ideaRequest = new IdeaRequest(); //업무 처리 defaultWorkHandler.handle(ideaRequest); //업무 처리자 출력 @@ -71,11 +71,11 @@ public class Main { =================== ``` -위의 코드는 간단하게 `DefaultWorkHandler.handle()`을 통해 개발자가 업무를 처리하는 기능을 구현했다. +위 코드에서 `DefaultWorkHandler.handle()`은 개발자가 업무 처리하는 기능이다. -그렇다면 이번엔 `개발 업무`를 진행하기전에 `기획 작업`이 추가돼야한다 가정하자. +이번엔 `개발 업무`를 진행하기전에 `기획 작업`이 추가된다 해보자. -아래 예제는 기존 코드에 기획 작업을 추가하는 2가지의 방법이다. +아래는 `기획 작업`을 추가하는 2가지의 방법이다. ```java 1. 기존 handle()에 기획 업무까지 처리하는 코드 @@ -112,13 +112,13 @@ public class DefaultWorkHandler { public void handle(IdeaRequest ideaRequest) { //기획 업무를 기존 개발 업무처리 메소드에 같이 처리 System.out.printf("%s 기획자가 업무를 처리했습니다\n", planner); - //기획자 등록 - ideaRequest.setPlanner(planner); + //기획자 등록 + ideaRequest.setPlanner(planner); - //개발 업무 처리 + //개발 업무 처리 System.out.printf("%s 개발자가 업무를 처리했습니다.\n", this.developer); //개발자 등록 - ideaRequest.setDeveloper(this.developer); + ideaRequest.setDeveloper(this.developer); } } @@ -171,17 +171,17 @@ public class PlaningWorkHandler extends DefaultWorkHandler{ public PlaningWorkHandler(String developer, String planner) { //부모 클래스에 개발자 전달 - super(developer); - //기획자 이름 저장 + super(developer); + //기획자 이름 저장 this.planner = planner; } @Override public void handle(IdeaRequest ideaRequest) { - //기획 업무 처리 + //기획 업무 처리 System.out.printf("%s 기획자가 업무를 처리했습니다.\n", this.planner); ideaRequest.setPlanner(this.planner); - //개발 업무 호출 + //개발 업무 호출 super.handle(ideaRequest); } } @@ -205,12 +205,13 @@ public class Main { =================== ``` -1번 방법 같은 경우 기존 `handle()` 메소드에 기획 업무까지 같이 처리하게끔 구현된 것을 볼 수 있는데, 이 경우 `단일 책임 원칙(SRP: Single Responsibility Principle)`을 위반하게 된다. +1번 방법은 기존 `handle()` 메소드에 기획 업무도 처리하도록 했지만, 이 경우 `단일 책임 원칙(SRP: Single Responsibility Principle)`을 위반하게 된다. + +2번 방법은 `DefaultWorkHandler`를 상속 받은 `PlaningWorkHandler`을 구현해 기존 코드가 수정되지 않았으므로 `SRP` 는 만족을 한다. 하지만 사용할 클래스 구현체를 클라이언트에서 직접 변경해줘야한다. -2번 방법은 `DefaultWorkHandler`를 상속 받은 `PlaningWorkHandler`을 구현함으로써 기존 코드를 수정하지 않고 기획 작업을 추가했으므로 `SRP` 는 만족을 한다. 하지만 내가 사용할 구체적인 클래스 구현체를 클라이언트에서 직접 변경해줘야한다. 예를 들어 **개발만 작업하고 싶으면** `DefaultWorkHandler`, **기획 작업을 추가하고 싶으면**`PlaningWorkHandler`를 사용해야한다는 것 즉, `커플링`이 높아지게 된다. -또한 추후에 `개발 작업`후 `QA테스트 작업`이 추가되거나, `기획 작업`을 제외하고 `개발 작업`과 `QA테스트`만 진행해야하는 경우마다 각각의 `WorkHandler`를 새로 작성해줘야하므로 매우 복잡해진다. +또 만약 `개발 작업`후 `QA테스트 작업`이 추가되거나, `기획 작업`을 제외하고 `개발 작업`과 `QA테스트`만 진행하는 경우 각각의 `WorkHandler`를 새로 작성해야하므로 복잡해진다. 이제 `책임 연쇄 패턴`을 적용해보자. @@ -238,6 +239,7 @@ public class IdeaRequest { /** * 각 작업의 공통 인터페이스 + * ** Handler ** */ public interface WorkHandler { void handle(IdeaRequest ideaRequest); @@ -245,6 +247,7 @@ public interface WorkHandler { /** * 개발 업무 처리 Handler + * ** ConcreteHandler ** */ class DeveloperWorkHandler implements WorkHandler { private WorkHandler workHandler; @@ -271,6 +274,7 @@ class DeveloperWorkHandler implements WorkHandler { /** * 기획 업무 처리 Handler + * ** ConcreteHandler ** */ public class PlaningWorkHandler implements WorkHandler { private String planner; @@ -296,6 +300,7 @@ public class PlaningWorkHandler implements WorkHandler { /** * QA 업무 처리 Handler + * ** ConcreteHandler ** */ public class TestWorkHandler implements WorkHandler{ private WorkHandler workHandler; @@ -321,6 +326,7 @@ public class TestWorkHandler implements WorkHandler{ /** * 메인 클래스 + * ** Client ** */ public class Main { private WorkHandler workHandler;