Lee's Grow up

[디자인패턴/Design Pattern] Chain of Responsibility 역할 사슬 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Pattern] Chain of Responsibility 역할 사슬 패턴

효기로그 2019. 12. 31. 10:46
반응형

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문],[Head First Design Pattern]의 내용을 참고해서 정리한 글입니다.
잘못된 부분은 댓글로 피드백 부탁드립니다.

1. Chain of Responsibility 패턴이란?


요청이 주어질때, 사슬에 속해있는 각 객체는 자기가 받은 요청을 검사하여 직접 처리하거나 사슬에 들어있는 다른 객체에 넘기게 되는 방식. 즉 책임을 넘기는 구조입니다.

2. Chain of Responsibility 패턴의 등장인물


  • Handler(처리자)의 역할
    1. 요구를 처리하는 인터페이스(API)를 결정하는 역할을 합니다.
  • ConcreteHandler(구체적인 처리자)의 역할
    1. 요구를 처리하는 구체적인 역할을 합니다.
  • Clinet(요구자)의 역할
    1. 최초의 ConcreteHandler 역할에 요구하는 일을 합니다.
Chain of Responsibility 클래스 다이어 그램

3. 예제


3-1. Trouble 클래스

무언가 요청을 표현할 클래스입니다.

public class Trouble {
    private int number;

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

    public int getNumber() {
        return number;
    }

    @Override
    public String toString() {
        return "Trouble [number=" + number + "]";
    }
}
3-2. Support 추상 클래스

트러블(요청)을 해결할 사슬을 만들기 위한 추상 클래스입니다. next라는 필드를 통해서 떠넘기는 곳을 지정하고, 필요하면 트러블(요청)을 떠넘기는 곳을 setNext() 메소드로 설정합니다.

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

    @Override
    public String toString() {
        return "Support [name=" + 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");
    }
}

support() 메소드를 통해서 트러블(요청)에 대한 처리 수순을 정의했습니다. 또한 실제 처리 부분인 resolve()메소드는 추상 메소드로 선언해 하위 클래스에서 구체적인 구현을 위임합니다.

3-3. NoSupport, LimitSupport, OddSupport, SpecialSupport 추상 클래스

각각 Support 추상 클래스의 하위 클래스입니다. 필요한 로직을 수행합니다.

public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }

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

public class OddSupport extends Support {

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

    @Override
    protected boolean resolve(Trouble trouble) {
        if(trouble.getNumber() % 2 == 1) {
            return true;
        } 
        return false;
    }
}

public class SpecialSupport extends Support {
    private int number;

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

    @Override
    protected boolean resolve(Trouble trouble) {
        if(trouble.getNumber() == number) {
            return true;
        }
        return false;
    }
}

public class LimitSupport extends Support {
    private int limit;

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

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            return true;
        }
        return false;
    }
}
3-4. Main 클래스

Support 클래스를 통해 사슬을 생성하고, 트러블(요청)을 수행하는 매인 클래스입니다.

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("Elomo");
        Support fred = new LimitSupport("Fred",300);

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

        for (int i = 0; i < 500; i++) {
            alice.support(new Trouble(i));    
        }
    }
}
실행 결과
Trouble [number=0] is resolved by Support [name=Bob]
Trouble [number=1] is resolved by Support [name=Bob]
Trouble [number=2] is resolved by Support [name=Bob]
....
Trouble [number=498] cannot be resolved
Trouble [number=499] is resolved by Support [name=Elomo]
그렇다면 왜 사용할까?
  • 요청을 보낸 쪽하고 받는 쪽을 분리시킬 수 있습니다.
  • 객체에서는 사슬의 구조를 몰라도 되고 그 사슬에 들어있는 다른 객체에 대한 직관적인 래퍼런스를 가질 필요도 없기 때문에 객체를 단순하게 만들 수 있습니다.
  • 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가/제거할 수 있습니다.

윈도우 시스템에서 마우스 클릭이나 키보드 이벤트를 처리할 때 흔하게 사용됩니다. 그러나 아래와 같은 단점도 존재합니다.

  • 실행시에 과정을 살펴보거나 디버깅하기가 힘들 수 있다는 단점이 있습니다.
  • 요청이 반드시 수행된다는 보장이 없습니다. 사슬의 끝이여도 어느 객체에서도 처리를 못하는 경우 ( 장점이 될 수도 )

4. 관련 패턴


  • Composite 패턴 : Handler 역할에 Composite 패턴이 자주 등장합니다. link
  • Command 패턴 : Hanlder 역할에 대해서 제공되는 '요구'에는 Command 패턴이 사용되는 경우가 있습니다.
반응형
Comments