🧵 Design Pattern
디자인 패턴 - Chain of Responsibility 패턴
date
Oct 9, 2022
slug
chain-of-responsibility-pattern
author
status
Public
tags
디자인패턴
Chain of Responsibility 패턴
summary
Chain of Responsibility 패턴에 대해 알아보자
type
Post
thumbnail
category
🧵 Design Pattern
updatedAt
Nov 22, 2023 01:11 AM
언어
개요
소프트웨어 마에스트로 과정에서 멘토님, 팀원들과 디자인패턴 세미나를 진행하고 있습니다. 이번에는 Chain of Responsibility 패턴에 대해 설명드리겠습니다.
💡 Java언어로 배우는 디자인 패턴 입문 책을 참고하여 포스팅하였습니다.
Chain of Responsibility Pattern?
어딘가에 문의 전화를 했다고 가정해봅시다. A라는 곳에 물어보았더니 B라는 곳으로 전화하라고 합니다. 다시 B에 문의했더니 C담당자에게 연락하라고 합니다. 이런 식으로 담당자를 찾을 때까지 다음 사람으로 자신의 요구가 차례로 넘겨지는 것을 책임 떠넘기기라고 합니다.
Chain of Responsibility 패턴은 이렇게 책임 떠넘기기 처럼 여러 객체를 연결해서 연결된 객체를 순회하며 요청을 처리할 객체를 결정하는 방법입니다. 이 패턴을 사용하면 요청하는 쪽과 처리하는 쪽의 연결을 유연하게 해서 각 오브젝트를 부품으로 독립시킬 수 있습니다.
예제 프로그램
해당 패턴의 예제 프로그램으로 트러블이 발생하여 누군가 처리해야 하는 상황을 생각해 봅시다.
등장하는 클래스는 아래 표와 같습니다.
이름 | 설명 |
Trouble | 발생한 트러블을 나타내는 클래스. 트러블 번호(number)를 가진다 |
Support | 트러블을 해결하는 추상 클래스 |
NoSupport | 트러블을 해결하는 구상 클래스 (항상 '처리하지 않는다') |
LimitSupport | 트러블을 해결하는 구상 클래스 (지정한 번호 미만의 트러블을 해결) |
OddSupport | 트러블을 해결하는 구상 클래스 (홀수 번호의 트러블을 해결) |
SpecialSupport | 트러블을 해결하는 구상 클래스 (특정 번호의 트러블을 해결) |
Main | Support들의 사슬을 만들고 트러블을 발생시키는 동작 테스트용 클래스 |
클래스 다이어그램은 아래와 같습니다.
Trouble 클래스
- 트러블 클래스는 발생한 트러블을 표현하는 클래스입니다.
public class Trouble { private int number; // 트러블 번호 public Trouble(int number) { // 트러블 생성 this.number = number; } public int getNumber() { // 트러블 번호를 얻는다. return number; } public String toString() { // 트러블의 문자열 표현 return "[Trouble " + number + "]"; } }
Support 클래스
- 트러블을 해결할 사슬(chain)을 만들기 위한 추상 클래스입니다.
- next 필드는 떠넘기는 곳을 지정하고 setNext 메소드는 떠넘기는 곳을 설정합니다.
- resolve 메소드는 하위 클래스에서 구현할 곳을 상정한 추상 메소드입니다. return 값이 false라면 다음 사람에게 떠넘기기를 합니다.
public abstract class Support { private String name; // 이 트러블 해결자의 이름 private Support next; // 떠넘기는 곳 public Support(String name) { // 트러블 해결자의 생성 this.name = name; } public Support setNext(Support next) { // 떠넘기는 곳을 설정 this.next = next; return next; } public final void support(Trouble trouble) { // 트러블 해결의 수순 if (resolve(trouble)) { done(trouble); } else if (next != null) { next.support(trouble); } else { fail(trouble); } } public String toString() { // 문자열 표현 return "[" + name + "]"; } protected abstract boolean resolve(Trouble trouble); // 해결용 메소드 protected void done(Trouble trouble) { // 해결 System.out.println(trouble + " is resolved by " + this + "."); } protected void fail(Trouble trouble) { // 미해결 System.out.println(trouble + " cannot be resolved."); } }
NoSupport 클래스
- Support 클래스의 하위 클래스입니다.
- NoSupport 클래스는 아무것도 문제를 처리하지 않는 클래스이므로 해당 클래스의 resolve 메소드는 항상 false를 반환합니다.
public class NoSupport extends Support { public NoSupport(String name) { super(name); } protected boolean resolve(Trouble trouble) { // 해결용 메소드 return false; // 난 아무것도 처리 안해! } }
LimitSupport 클래스
- limit에서 지정한 번호 미만의 트러블을 해결하는 클래스입니다.
public class LimitSupport extends Support { private int limit; // 이 번호 미만이면 해결! public LimitSupport(String name, int limit) { // 생성자 super(name); this.limit = limit; } protected boolean resolve(Trouble trouble) { // 해결용 메소드 return trouble.getNumber() < limit; } }
OddSupport 클래스
- 홀수 번호의 트러블을 처리하는 클래스입니다.
public class OddSupport extends Support { public OddSupport(String name) { // 생성자 super(name); } protected boolean resolve(Trouble trouble) { // 해결용 메소드 return trouble.getNumber() % 2 == 1; } }
SpecialSupport 클래스
- 지정 번호의 트러블ㄹ에 한해 처리하는 클래스입니다.
public class SpecialSupport extends Support { private int number; // 이 번호만 해결해! public SpecialSupport(String name, int number) { // 생성자 super(name); this.number = number; } protected boolean resolve(Trouble trouble) { // 해결용 메소드 return trouble.getNumber() == number; } }
Main 클래스
- Alice ~ Fred까지 6명의 트러블 해결자를 작성하고 setNext 메소드를 사용하여 이들을 일렬로 나열합니다.
public class Main { public static void main(String[] args) { Support alice = new NoSupport("Alice"); Support bob = new LimitSupport("Bob", 100); Support charlie = new SpecialSupport("Charlie", 429); Support diana = new LimitSupport("Diana", 200); Support elmo = new OddSupport("Elmo"); Support fred = new LimitSupport("Fred", 300); // 사슬의 형성 Support support = alice .setNext(bob) .setNext(charlie) .setNext(diana) .setNext(elmo) .setNext(fred); // 다양한 트러블 발생 for (int i = 0; i < 500; i++) { alice.support(new Trouble(i)); } } }
아래는 해당 예제의 시퀀스 다이어그램입니다.
등장인물
- Handler(처리자)의 역할
Handler는 요구를 처리하는 인터페이스를 결정하는 역할을 합니다. '다음사람'을 준비해 두고 자신이 처리할 수 없는 태스크라면 그 사람에게 떠넘기기를 합니다. 예제 프로그램에서는 Support 클래스가 이 역할을 합니다.
- ConcreteHandler(구체적인 처리자)의 역할
요구를 처리하는 구체적은 역할을 합니다. 예제 프로그램에서는 NoSupport, LimitSupport, OddSupport, SpecialSupport의 각 클래스가 이 역할을 합니다.
- Client(요구자)의 역할
Client는 최초의 ConcreteHnaler 역할에 요구하는 일을 합니다. 예제 프로그램에서는 Main 클래스가 이 역할을 합니다.
장단점
- 요구하는 사람과 요구를 처리하는 사람을 유연하게 연결해줍니다.
Client의 역할은 최초의 사람에게 요구를 하는 것입니다. 뒷일은 사슬 안으로 그 요구가 전달되어 적절한 처리자에 의해 요구가 처리됩니다. 해당 패턴을 사용하지 않을 경우 '이 요구는 이 사람이 해야해!'라는 정보를 누군가 중앙집권적으로 갖고 있어야 합니다. 그 정보를 요구하는 사람, 즉 Client에게 갖게 할 경우 부품으로써의 독립성이 훼손됩니다.
- 동적으로 사슬의 형태를 바꿉니다.
예제 프로그램과 달리 ConcreteHandler 역할의 오브젝트 관계가 동적으로 변할 수도 있습니다. 책임 떠넘기기 패턴을 사용하면 상황의 변화에 따라서 ConcreteHandler 역할을 재편할 수도 있습니다.
- 자신의 일에 집중할 수 있습니다.
각각의 ConcreteHandler 역할은 자신이 할 수 있는 역할에 집중하고 자신이 할 수 없으면 간단하게 다음 사람에게 전달합니다. 해당 패턴을 사용하지 않을 경우 한 명이 누가 요구를 처리할지 전부 결정해야 하며 일의 분담까지 각각의 ConcreteHandler 역할에게 부담시켜야 합니다.
- 떠넘기기로 처리가 지연될 수 있습니다.
누가 요구를 처리할 것인지 미리 정해져 있고 그 상대가 바로 처리하는 경우에 비해 느릴 수 있습니다. 하지만 이것은 트레이드 오프의 문제입니다. 요구와 처리자의 관계가 고정적이지 않고 처리 속도가 중요한 경우에는 해당 패턴을 사용하지 않는 편이 유효한 경우도 있습니다.