티스토리 뷰
소프트웨어 아키텍처의 규모가 커지고, 개발자 간 업무를 분배하여 일을 진행하는 경우가 많아졌습니다. 혼자 기능을 전부 구현한다면, 편하게 코드를 작성해도 되지만, 여러 사람에게 일감을 나눈 경우에는 개발, 소스코드 병합, 배포 등을 체계적인 절차로 일을 진행해야 합니다. 디자인 패턴은 객체 지향 프로그래밍을 할 때, 클래스 간 결합도를 낮추고 응집성을 높이기 위한 코드 작성 패턴이므로, 여러 개발자에게 업무를 나누어 동시에 개발을 진행할 수 있습니다. 이번 포스팅에서는 디자인 패턴 중 책임 연쇄에 관하여 알아보고, 이를 코드로 구현하는 방법에 대해 알아보겠습니다.
책임 연쇄 (Chain of Responsibility) 패턴
책임 연쇄 패턴은 디자인 패턴 중 행동 패턴에 속합니다. 행동 패턴은 클래스 또는 객체의 책임을 어떻게 분배할 것인지 고려하여 만들어진 패턴입니다. 책임 연쇄는 말 그대로, 객체가 처리해야 할 내용이 아니면 다른 객체에게 책임을 위임하는 행위입니다. 조금 더 직관적으로 설명드리면, 폭탄 돌리기 게임처럼 본인이 처리해야 할 메시지가 아니라면 다른 사람에게 처리를 요청하는 것입니다. 책임 연쇄 패턴의 특징을 요약하면 아래와 같습니다.
- 요청 객체와 처리 객체를 분리하여 결합도가 낮은 디자인 패턴이다
- 처리 객체는 인접한 객체에게 책임을 전달할 수 있다
책임 연쇄 패턴을 구현하면, 소스코드의 실행 흐름은 아래와 같습니다. 요청자가 관리자 메서드를 호출하면, 관리자는 실무자 객체에게 처리 요청 메서드를 호출하고, 실무자 1의 담당이 아니라면 실무자 2에게 다시 요청하고, 실무자 2도 동일하게 실무자 3에게 처리를 위임합니다. 앞에서 표현한 것처럼, 폭탄 돌리기 게임과 유사합니다.
책임 연쇄 패턴의 실행 흐름을 이해했으니, 해당 패턴의 장점에 대해 알아보겠습니다. 책임 연쇄 패턴을 구현하였을 때 얻을 수 있는 장점과 단점은 다음과 같습니다.
- 클래스 또는 객체 간 결합도를 낮출 수 있어, 유지보수에 도움이 되는 구조이다
- 여러 개의 객체 중에서 어떤 객체가 요구를 처리할 수 있는지 사전에 알 수 없을 때 문제를 해결할 때 사용하는 코드 패턴이다
- 처리 객체를 찾기 위해, 객체 순회를 해야 하는 불필요한 반복 연산이 포함된다
- 처리 객체의 연결 구성을 잘못하면, 사이클이 발생할 수 있다
예제 작성 및 실행
디자인 패턴은 수학공식처럼 코드를 어떻게 작성해야 한다는 정답은 없습니다. 위의 실행 흐름만 준수한다면, 책임 연쇄 처리 패턴을 구현한 것입니다. 위 그림의 실행 로직을 자바 소스코드로 구현해보겠습니다. 요청 객체는 Main 클래스로 작성하고, 처리 객체는 Manager와 Engineer 클래스로 작성하였습니다.
public abstract class Manager {
Manager nextNode;
protected int assignedNum;
protected String name;
public Manager setNext(Manager next) {
Manager m = this;
while (m.nextNode != null)
m = m.nextNode;
m.nextNode = next;
return this;
}
public boolean handlerRequest(int requestNum, String request) {
if (assignedNum == requestNum) {
processRequest(request);
return true;
} else {
System.out.println(requestNum + " cannot assign to " + name + ". Request Another node");
return nextNode != null ? nextNode.handlerRequest(requestNum, request) : false;
}
}
abstract void processRequest(String request);
}
Manager클래스는 Engineer클래스에게 책임을 위임하는 역할을 합니다. setNext 메서드를 통해, Engineer 객체 간 이동할 수 있도록 설정합니다. 마치, 선형 링크드 리스트를 구현할 때와 동일한 소스 코드 구조입니다. handlerRequest 메서드는 현재 참조하고 있는 객체가 담당이 아니면, 다음 객체에게 처리를 요청하는 방식으로 책임을 위임합니다. processRequest는 처리 객체가 구현해야 할 공통 메서드입니다. 다음으로 처리 객체인 Engineer 클래스를 살펴보겠습니다.
public class Engineer1 extends Manager {
public Engineer1(int assignNum) {
this.assignedNum = assignNum;
this.name = "Engineer1";
}
@Override
void processRequest(String request) {
System.out.println("Process " + this.name + " : " + request);
}
}
public class Engineer2 extends Manager {
public Engineer2(int assignNum) {
this.assignedNum = assignNum;
this.name = "Engineer2";
}
@Override
void processRequest(String request) {
System.out.println("Process " + this.name + " : " + request);
}
}
public class Engineer3 extends Manager {
public Engineer3(int assignNum) {
this.assignedNum = assignNum;
this.name = "Engineer3";
}
@Override
void processRequest(String request) {
System.out.println("Process " + this.name + " : " + request);
System.out.println("Ok. I will give you 1 million dollar.");
}
}
Manager 클래스와 달리, Enginner 1~3 클래스는 요청을 처리하는 코드만 작성되어 있습니다. Manager는 Engineer에게 처리 담당인지 확인하여 결과를 반환해야 할 의무가 있는 클래스라면, Engineer는 단지 처리하는 방법에 대해서만 기술하고 있는 클래스입니다. 상속을 통해 클래스 간 결합도를 낮춘 케이스입니다.
import com.company.chainofresponsibility.Engineer1;
import com.company.chainofresponsibility.Engineer2;
import com.company.chainofresponsibility.Engineer3;
import com.company.chainofresponsibility.Manager;
public class Main {
public static void main(String[] args) {
Manager manager = new Engineer1(1)
.setNext(new Engineer2(2))
.setNext(new Engineer3(3));
System.out.println("Request Msg : Please give me money");
if(manager.handlerRequest(3,"Please give me money")) {
System.out.println("Thanks You.");
} else {
System.out.println("You are bad guy.");
}
}
}
여기서 Main클래스는 요청자에 해당합니다. Manager 객체를 생성할 때, Engineer객체를 생성하고, 바로 setNext 메서드를 통해 객체 간의 연결을 맺어줍니다. 그리고, handlerRequest 메서드를 통해 어느 처리 객체가 메시지를 처리하는지 확인 가능합니다. 작성한 소스코드가 앞서 살펴본 실행 순서와 동일하게 진행되는지 확인해보겠습니다.
메시지 처리 순서를 보면, Engineer1과 2에게 처리를 요청하였지만 처리 담당이 아니므로, 다음 객체에게 책임을 위임을 하다가 Engineer3 객체가 마지막으로 최종 처리하는 결과를 확인할 수 있습니다.
'IT 기술' 카테고리의 다른 글
[Node.js] Promise.all을 활용한 비동기 코드 작성법 (0) | 2022.09.16 |
---|---|
[Node.js] Promise, Await, Async로 동기, 비동기 작성하기 (0) | 2022.09.16 |
구글 애드센스 승인 후기 (22년 6월 기준) (0) | 2022.09.13 |
[Node.js] 비동기 처리 로직의 실행 원리 분석 (0) | 2022.09.13 |
[Node.js] 멀티 쓰레드, 멀티 프로세스로 병렬 처리하기 (0) | 2022.09.13 |
- Total
- Today
- Yesterday
- TCP/UDP
- API Gateway 캐싱
- 테스팅 자동화
- 스프링부트 restapi
- OSI 7계층
- notion
- springboot build
- 디자인패턴 구조패턴
- 클린코드작성법
- springboot 실행
- MPA
- token
- SPA
- codesmell 유형
- springboot rest api 서버
- 코드스멜 유형
- 스프링부트실행
- 멀티코어 멀티프로세서
- 디자인패턴
- iframe 태그 찾기
- springboot restapi
- python
- selenium
- 클린코드작성원칙
- oracle pga sga
- C++
- notion 업무일정관리
- 스프링부트빌드
- oracle 메모리
- AWS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |