Design Pattern | Chain of Responsibility (책임 연쇄)

Chain of Responsibility 패턴이란?

  • Chain이라는 영어 단어는 연쇄, Responsibility라는 영어 단어는 책임, 즉 Chain of Responsibility는 책임의 연쇄라는 의미가 된다. 실제로는 회전하는 구조라고 생각하는 것이 이해하기 쉬울 것이다.
  • Chain of Responsibility 패턴은, 여러개의 객체를 체인으로 연결하여 그 객체의 체인을 순차적으로 걸어 가면서 최종적인 객체를 결정하는 방식이다.
  • 어떤 사람에게 요구 사항이 온다고 하였을 때, 그 사람이 바로 그것을 처리할 수 있으면 처리한다. 처리할 수 없다면 그 요구를 ‘다음 사람’으로 전달한다. 이후 계속적으로 반복하는 것이 Chain of Responsibility 패턴이다.
  • GoF의 디자인 패턴에서는 행위에 대한 디자인 패턴으로 분류된다.

Chain of Responsibility 패턴 예제 프로그램

입력한 문제를 어느 하나의 지원으로 해결하는 프로그램이다.

Class Diagram
Chain of Responsibility Pattern Class Diagram

1. Trouble 클래스

발생한 문제를 나타내는 클래스이다.

Trouble.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class Trouble {

    // 트러블 번호
    private int number;

    public Trouble(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public String toString() {
        return "[Trouble " + number + "]";
    }
}

2. Support 클래스

문제를 해결하는 추상 클래스이다.

Support.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

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 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.");
    }
}

3. NoSupport 클래스

트러블을 해결하는 구상 클래스이다. 항상 처리하지 않는다.

NoSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class NoSupport extends Support {

    public NoSupport(String name) {
        super(name);
    }

    protected boolean resolve(Trouble trouble) {
        return false;
    }
}

4. LimitSupport 클래스

트러블을 해결하는 구상 클래스이다. 지정한 번호 미만의 문제를 해결할 수 있다.

LimitSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class LimitSupport extends Support {

    private int limit;

    public LimitSupport(String name, int limit) {
        super(name);
        this.limit = limit;
    }

    // limit 미만이면 해결 가능
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            return true;
        } else {
            return false;
        }
    }
}

5. OddSupport 클래스

트러블을 해결하는 구상 클래스이다. 홀수 번호 문제를 해결할 수 있다.

OddSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

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;
        }
    }
}

6. SpecialSupport 클래스

트러블을 해결하는 구상 클래스이다. 특정 번호의 문제를 해결할 수 있다.

SpecialSupport.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

public class SpecialSupport extends Support {

    private int number;

    public SpecialSupport(String name, int number) {
        super(name);
        this.number = number;
    }

    // number와 같으면 해결 가능
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number) {
            return true;
        } else {
            return false;
        }
    }
}

7. Main 클래스

메인 처리를 실행하는 클래스이다.

Main.java

package com.devkuma.designpattern.behavioral.chainofresponsibility;

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");

        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo);

        // 다양한 문제 발생
        for (int i = 0; i < 500; i += 33) {
            alice.support(new Trouble(i));
        }
    }
}

8. 실행 결과

[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] cannot be resolved.
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

Chain of Responsibility 패턴의 장점 및 주의점

Chain of Responsibility 패턴의 포인트는 “요구를 내는 사람(Main)“과 “요구를 처리하는 사람(Support)“을 완만하게 연결시키는 것에 있다. “요구를 내는 사람(Main)“은 최초의 사람에게 요구를 내면, 나머지는 체인 안을 그 요구가 흘러 가게 되어, 적절한 사람에 의해 요구가 처리된다.
Chain of Responsibility 패턴을 사용하지 않으면 누군가가 “이 요청은 이 사람이 처리하는 것"이라는 정보를 가지고 있어야 한다. 그 정보를 “요구를 내는 사람(Main)“이 갖게 해버리면, 부품으로서의 독립성이 손상된다.
주의점으로서는 Chain of Responsibility 패턴은 유연성이 높아지만, 돌려보내는 만큰 처리는 느려진다. 처리 속도가 매우 중요한 경우에는 Chain of Responsibility 패턴을 사용하지 않는 것이 좋다.