title | excerpt | toc | toc_sticky | toc_label | header | categories | tags | last_modified_at | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
[디자인 패턴] Chapter 14. Chain of Responsibility 패턴 |
Hiroshi Yuki, Java 언어로 배우는 디자인 패턴 입문 |
true |
true |
Ch14. Chain of Responsibility 패턴 |
|
|
|
2020-05-19 23:00:00 +0900 |
-
"책임 떠넘기기"가 필요한 경우가 있다
- ex) 어떤 요구가 발생했을 때 그 요구를 처리할 객체를 바로 결정할 수 없는 경우에는 다수의 객체를 사슬처럼 연결해 두고 객체의 사슬을 차례로 돌아다니면서 목적에 맞는 객체를 결정하는 경우
-
Chain of Responsibility
- 어떤 사람에게 요구가 들어온다 ➔ 그 사람이 그것을 처리할 수 있으면 처리하고, 처리할 수 없으면 '다음 사람'에게 넘긴다
- trouble이 발생해서, 누군가가 처리해야 하는 상황
이름 | 해설 |
---|---|
Trouble | 발생한 트러블을 나타내는 클래스. 트러블 번호(number)를 가짐 |
Support | 트러블을 해결하는 추상 클래스 |
NoSupport | 트러블을 해결하는 구상 클래스(항상 '처리하지 않음') |
LimitSupport | 트러블을 해결하는 구상 클래스(지정한 번호 미만의 트러블을 해결) |
OddSupport | 트러블을 해결하는 구상 클래스(홀수 번호의 트러블을 해결) |
SpecialSupport | 트러블을 해결하는 구상 클래스(지정 번호의 트러블을 해결) |
Main | Support들의 사슬을 만들어 트러블을 일으키는 동작 테스트용 클래스 |
- 클래스 다이어그램
- number 필드: 트러블 번호를 유지함
- getNumber()
- 트러블 번호를 반환하는 메소드
public class Trouble {
private int number; // 트러블 번호
public Trouble(int number) { // 트러블 번호 생성
this.number = number;
}
public int getNumber() {
return number;
}
public String toString() {
return "[Trouble " + number + "]";
}
}
- 트러블을 해결할 사슬을 만들기 위한 추상 클래스
- name 필드: 트러블 해결자의 이름
- next 필드:
- 자기 다음 번에 트러블을 해결할 객체(떠넘기기를 할 곳)를 가리킨다
- setNext()
- next 필드를 설정한다
- support()
- 추상 메소드 resolve() 메소드를 호출해서, 반환 값이 false 이면, 자기 다음 해결자에게 트러블을 떠넘긴다
- Template Method 패턴을 사용했다
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.");
}
}
- Suport의 하위 클래스
- resolve()
- 항상 false를 반환
- 즉, 어떠한 트러블도 해결하지 않음
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
return false;
}
}
- Support의 하위 클래스
- 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) {
if (trouble.getNumber() < limit) {
return true;
} else {
return false;
}
}
}
- 홀수 번호의 트러블을 처리하는 클래스
public class OddSupport extends Support {
public OddSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}
- 지정한 번호의 트러블에 한해서 처리하는 클래스
public class SpecialSupport extends Support {
private int number;
public SpecialSupport(String name, int number) {
super(name);
this.number = number;
}
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}
- 알고리즘
- 여섯 명의 트러블 해결자 생성
- Chain of Responsibility 형성
- 다양한 트러블 발생시켜, Alice에게 전달
- 결과
- 처음에는 Bob이 분발한다
- Bob이 해결할 수 없을 때 Diana가 등장
- Alice는 전혀 등장하지 않는다(모든 트러블을 떠넘기므로)
- 429번 트러블은, Charlie가 해결한다
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);
// 사슬 형성
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
// 트러블 생성
for (int i=0; i<500; i+=33) {
alice.support(new Trouble(i));
}
}
}
- 363번 트러블을 처리할 때의 시퀀스 다이어그램
- 클래스 다이어그램
-
요구하는 사람과 요구를 처리하는 사람을 유연하게 연결
- Client는 맨 처음 사람에게만 요구를 한다
- 그 요구가 처리자들을 연결한 사슬을 돌아다니다가
- 적절한 처리자에 의해 처리된다
- 이 패턴을 사용하지 않는다면, "이 요구는 이 처리자가 처리해야 한다"라는 지식을 누군가 중앙집권적으로 가지고 있어야 한다
- 이 지식을 요구자에게 맡기는 것은 현명하지 않다
-
동적으로 사슬의 형태를 바꿀 수 있다
- Alice ~ Fred 까지의 서포트 팀이 항상 고정되는 것은 아니다
- 동적으로 처리자들의 순서를 바꿀 수 있다
-
자신의 일에 집중할 수 있다
- 자신이 할 수 없으면 재빨리 '다음 사람'에게 넘김으로써, 각각의 처리자가 자신의 일에만 집중할 수 있다
-
떠넘기기로 처리가 늦어지지 않을까?
- 유연성은 높지만, 처리 속도는 느리다
- 요구와 처리자의 관계가 고정적이고, 처리의 속도가 중요한 경우에는 이 패턴을 사용하지 않는 편이 유효할 수 있다
- Composite 패턴
- Command 패턴
- 요구를 처리하는 객체들을 사슬 모양으로 늘어놓고, 요청이 들어오면 누가 처리할 지를 차례차례 체크해 나가는 Chian of Responsibility 패턴
- 14-1
- 윈도우 시스템에서, Chain of Responsibility 패턴이 자주 사용된다
- 윈도우 시스템의 버튼, 텍스트박스, 체크 박스 등에 마우스 클릭 이벤트가 발생하면?
-
14-2
- 윈도우 시스템의 또 다른 Chain of Responsibility 패턴 사용 사례
- 그림 14-5의 작은 다이얼로그
- '폰트' 리스트 박스에 포커스가 있을 때 화살표 키를 움직이면 리스트 박스 안의 선택되는 폰트가 변함
- 체크 박스에 포커스가 있을 때 ↑키를 누르면 포커스가 '폰트'리스트로 이동해서 ↓키를 눌러도 체크 박스로 포커스가 돌아오지 않음
- Chain of Responsibility 패턴 적용
-
14-3
- Support 클래스의 support 메소드는 public 으로 선언된 반면,
- resolve 메소드는 protected로 선언된 의도는 무엇일까?
-
14-4
- 예제 프로그램의 Support 클래스의 support 메소드를, 재귀적으로 호출하지 말고, for-loop로 전개하시오
-
14장 에제 프로그램에, HwSupport 클래스 추가하기
- 기능: 트러블 번호가 소수(prime number)이면 자신이 처리하고, 소수가 아니면 다음 처리자에게 넘긴다
- "소수 구하는 메소드 calcPrimeNumber(int)"를 HwSupport에 작성할 것
- Main의 main()에서는
- '자신의 이름'을 객체 참조 변수로 하여, HwSupport 객체를 생성한다
- 예: Support seongwon = new HwSupport("장성원");
- 이 객체를, 책임 사슬의 맨 앞에 둔다
- 나머지는 예제 프로그램과 그대로 둔다
- '자신의 이름'을 객체 참조 변수로 하여, HwSupport 객체를 생성한다
-
숙제 제출 방법
- 보고서
- 표지 (과목명, 학번, 이름, 제출일, 담당교수 등)
- 코드 설명 (표를 이용하는 등 보기 좋게 작성)
- 각 클래스가 하는 일
- 각 클래스의 각 메소드가 하는 일
- 코드 프린트
- 프로그램
- 해람인의 e참뜰 과제물 제출 기능을 이용
- 바이러스 체크 후 압축파일로 제출
- 각 클래스의 소스 파일 (java)
- 모든 클래스 컴파일 한 파일 (class)
- 해람인의 e참뜰 과제물 제출 기능을 이용
- 보고서