(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다
#1. 객체 생성 관련 패턴 |
#2. 구조 관련 패턴 |
#3. 행동 관련 패턴 |
||
✔️ 책임 연쇄 패턴이란 (Chain of Responsibility patterns)
책임 연쇄 패턴에서의 책임이란, 객체지향 5가지 원칙 중 - 단일 책임 원칙에서 말하는 책임과 동일한 의미를 내포합니다.
- 👏🏻 책임 연쇄 패턴이란, 클라이언트의 요청을 처리할 수 있는 책임을 가지는 처리객체를 집합(Chain)으로 만들어 부여함으로써 결합을 느슨하게 만들기 위한 디자인 패턴입니다.
- 일반적으로 요청을 처리할 수 있는 객체를 찾을 떄 까지 집합 안에서 요청을 전달합니다.
- 요청이 들어오면, 요청을 처리하는 책임을 가지는 handler가 체인처럼 연결되어서 각 요청에대한 책임을 처리합니다.
- 따라서 클라이언트는 내부의 구체적인 구현 코드를 알지 못하도록 하는 목적또한 가지는 패턴입니다.
✔️ 책임 연쇄 패턴 예시
1) 책임 연쇄 패턴 적용 전
- 클라이언트가 어떤 요청을 할때, 요청을 처리하기 전에 이 클라이언트에대해 인증에 대한 기능을 해야한다고 가정합니다.
public class Client {
public static void main(String[] args) {
Request request = new Request("무궁화 꽃이 피었습니다.");
RequestHandler requestHandler = new RequestHandler();
//클라이언트가 요청
requestHandler.handler(request);
}
}
@Getter
public class Request {
private final String time;
private final String body;
public Request(String body) {
this.body = body;
this.time = LocalDateTime.now().toString();
}
}
- 요청을 처리하는 객체의 기능안에 "특정한 인가의 기능" 을 추가해도 되지만 이렇게 하면 → 단일책임 원칙에 위배됩니다.
@Value
public class RequestHandler {
public void handler(Request request) {
//무언가 인증이 필요해서 코드에 넣을 때 → 단일책임에 위배
System.out.println("이 유저는 인증이 되었나?");
System.out.println("request = " + request.getBody());
}
}
↓
AuthRequestHandler.class
- 구체적인 기능에대한 책임을 하위 클래스로 추출하면 단일책임 원칙에 위배되지 않게 수정할 수 있습니다.
public class RequestHandler {
public void handler(Request request) {
System.out.println("request = " + request.getBody());
}
}
public class AuthRequestHandler extends RequestHandler {
public void handler(Request request) {
//무언가 인증이 필요해서 코드에 넣을 때 → 단일책임에 위배
System.out.println("이 유저는 인증이 되었나?");
super.handler(request);
}
}
Client.class
- 하지만 클라이언트에서 구체적인 기능을 가지는 클래스를 호출해야합니다.
public class Client {
public static void main(String[] args) {
Request request = new Request("무궁화 꽃이 피었습니다.");
//클라이언트가 구체적인 클래스를 선택
RequestHandler requestHandler = new AuthRequestHandler();
//클라이언트가 요청
requestHandler.handler(request);
}
}
↓
LoggingHandler.class
- 또한 로깅같은 특정한 기능을 선택하고 싶을때, 또다시 클라이언트의 코드가 변경되어야하고
- "인증" or "로깅" 2가지 기능을 동시에 처리하고 싶을때 새로운 클래스가 생성되는 등 복잡한 처리를 진행해야합니다.
public class LoggingRequestHandler extends RequestHandler{
@Override
public void handler(Request request) {
System.out.println("로깅");
super.handler(request);
}
}
Client.class
public class Client {
public static void main(String[] args) {
Request request = new Request("무궁화 꽃이 피었습니다.");
//클라이언트가 구체적인 클래스를 선택
RequestHandler requestHandler = new AuthRequestHandler();
//로깅을 할려면 또다른 클래스를 선택
//RequestHandler requestHandler = new LoggingRequestHandler();
//→ 하지만 2가지 기능을 동시에 하고싶을때 복잡해짐
//클라이언트가 요청
requestHandler.handler(request);
}
}
📌 이러한 문제점을 책임연쇄 패턴으로 클라이언트는 구체적인 내부 구현체를 알지 못하게 하여 변경을 줄이고
📌 각 책임을 가지는 객체들을 연결하여 다중 기능을 처리할 수 있도록 개선할 수 있습니다.
2) 책임 연쇄 패턴 적용 후
- 책임 연쇄 패턴의 한 구조입니다.
- 먼저 각 행위를 캡슐화하고 → 캡슐화한 객체를 추상화하고 다음 행위를 호출하기 위한 추상클래스를 정의하겠습니다.
public abstract class RequestHandler {
//다음 핸들러를 호출하도록 이어지게
private RequestHandler nextHandler;
RequestHandler(RequestHandler nextHandler) {
this.nextHandler = nextHandler;
}
public void handler(Request request) {
//마지막이 아닐경우
if (nextHandler != null) {
nextHandler.handler(request);
}
}
}
- 그리고 특정기능에 대한 책임을 가지는 구체적인 클래스를 캡슐화하여 구현합니다.
- 핸들러(하위 클래스) 는 본인의 책임이 끝나면 부모의 handler 호출 함수를 호출합니다.
public class AuthRequestHandler extends RequestHandler {
public AuthRequestHandler(RequestHandler nextHandler) {
super(nextHandler);
}
@Override
public void handler(Request request) {
System.out.println("인증 ,,,,중... 완료!");
super.handler(request);
}
}
public class LoggingRequestHandler extends RequestHandler{
@Override
public void handler(Request request) {
System.out.println("로깅");
super.handler(request);
}
}
public class PrintRequestHandler extends RequestHandler {
public PrintRequestHandler(RequestHandler nextHandler) {
super(nextHandler);
}
@Override
public void handler(Request request) {
System.out.println("request.getBody() = " + request.getBody());
super.handler(request);
}
}
- 그런다음 클라이언트의 변경을 최소화하기 위해 Handler 를 외부로부터 주입받고
- 외부에서는 각 책임을 연결한 Handler를 Client 에게 주입합니다.
public class Client {
private RequestHandler requestHandler;
//요청을 처리할 Handler 를 주입받을
public Client(RequestHandler requestHandler) {
this.requestHandler = requestHandler;
}
public void doWork() {
Request request = new Request("이번 놀이는 뽑기입니다.");
requestHandler.handler(request);
}
public static void main(String[] args) {
//각 책임이 연결되게
RequestHandler chain = new AuthRequestHandler(new LoggingRequestHandler(new PrintRequestHandler(null)));
Client client = new Client(chain);
}
}
- 👏🏻이렇게 되면 각 책임에대한 기능을 클라이언트에게 주입하는 쪽에서 부분적으로 커스텀할 수 있고
- 클라이언트는 내부적인 구현을 전혀 알지 못하게 됩니다.
✔️ 책임 연쇄 패턴 장단점
장점
- 결합도를 낮추며, 요청의 발신자와 수신자를 분리시킬 수 있습니다.
- 클라이언트는 처리객체의 집합 내부의 구조를 알 필요가 없습니다.
- 집합 내의 처리 순서를 변경하거나 처리객체를 추가 또는 삭제할 수 있어 유연성이 향상됩니다.
- 새로운 요청에 대한 처리객체 생성이 매우 편리해집니다.
단점
- 충분한 디버깅을 거치지 않았을 경우 집합 내부에서 사이클이 발생할 수 있습니다.
- 디버깅 및 테스트가 쉽지 않습니다.
이렇게 해서 디자인 패턴의 행위 패턴 중 하나인 "책임 연쇄 패턴"에 대해서도 공부를 해보았습니다
Spring 에서는 Spring Security 의 인증, 인가 혹은 Dispathcher Servlet 쪽에서 HTTP Request 요청을 처리하는 부분에서 이러한 형태의 패턴을 띄우고있었던거 같습니다
자주 사용되는 패턴인만큼 한번 알아두니까 좋네요!
끝!
'Java > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 - 옵저버(관찰자) 패턴 (Observer Pattern) (0) | 2023.04.13 |
---|---|
[디자인 패턴] 행동 패턴 - 메멘토 패턴 (Memento Pattern) (0) | 2023.04.12 |
[디자인 패턴] 행동 패턴 - 중재자 패턴 (Mediator Pattern) (0) | 2023.04.10 |
[디자인 패턴] 행동 패턴 - 이터레이터 패턴 (반복자 패턴 - Iterator Pattern) (0) | 2023.04.04 |
[디자인 패턴] 행동 패턴 - 인터프리터 패턴 (Interpreter Pattern) (0) | 2023.03.31 |