Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

책임 연쇄 패턴 정리 #97

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 350 additions & 0 deletions 행동/7주차-책임 연쇄/summary/example-code.md
Original file line number Diff line number Diff line change
@@ -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()` 만 호출함으로써 알아서 체이닝된 업무들이 실행될 것이다. 또한 **필요한 업무만 체이닝하거나 순서도 바꿔서 사용할 수 있게 된다.**
Loading