본문 바로가기
Develop Story/DesignPattern

Chain of Responsibility Pattern

by 박은유 2024. 4. 8.
반응형

책임 연쇄 패턴(Chain Of Responsibility Pattern, COR)

 : 요청에 대한 처리를 하나의 객체가 몽땅 하는게 아닌, 여러개의 객체를 사슬처럼 연결해 연쇄적으로 처리하는 패턴입니다.

 이러한 처리 객체들을 핸들러(Handler)라고 하고,각 핸들러는 요청을 처리할 수 없으면 다음 핸들러에 책임을 '떠넘기는'패턴입니다.

 

떠넘긴다?

 '떠넘긴다'라는 말은 부정적으로 들리지만, 하나의 객체를 처리하는 책임을 요청하는 쪽처리하는 쪽을 분리하여 결합도를 느슨하게 만들고, 처리할 객체를 변경할 경우에도 유연하게 대처가 가능하다는 의미입니다.주로 if-else문을 최적화하는데 있어 실무에서 많이 사용하는 패턴입니다.

 

예를 들어보겠습니다.

 

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 + "]";
    }
}

 

위의 클래스는 발생한 트러블을 표현하는 클래스입니다. 이 트러블 번호에 대한 관리를 COR을 사용해 처리해보겠습니다.

 

public abstract class Support {
    private String name; //이 트러블 해결자의 이름
    private Support next; //떠넘길 곳
    
    public Support(String name) {
        this.name = name;
        this.next = null;
    }
    
    //떠넘길 곳을 설정한다
    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);
        }
    }
    
    //트러블 해결자의 문자열 표현
    @Override
    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");
    }
}

 

Support 클래스는 사슬을 만들기 위한 추상 클래스입니다. next는 "떠넘길 곳"을 나타내고, resolve 메소드는 "트러블을 해결하는"처리를 나타냅니다. 반환값에 따라 해결되었는지 아직 해결되지 않았는지 나타냅니다.

 

support메서드는 첫 번째로 resolve를 호출한 후 반환값이 true면 해결됐다는 done을 호출하고, false면 다음에게 "떠넘깁니다." 다음 해결자가 없으면(next==null)아무도 해결하지 못했으므로 fail을 호출합니다.

 

이제 Support의 하위클래스를 만들어보겠습니다.

 

public class NoSupport extends Support{
    public NoSupport(String name) {
        super(name);
    }
    
    @Override
    protected boolean resolve(Trouble trouble) {
        return false; // 자신은 아무것도 해결하지 않는다
    }
}

 

NoSupport 클래스의 resolve메소드는 항상 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;
        } else {
            return false;
        }
    }
}

 

LimitSupport클래스는 지정한 번호 미만의 문제를 해결하는 클래스입니다.

 

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

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

 

OddSupport클래스는 홀수 번호 문제를 해결하는 클래스입니다.

 

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;
        } else {
            return false;
        }
    }
}

 

SpecialSupport 클래스는 지정한 번호의 문제에 한해 해결하는 클래스입니다.

 

자 이제 하위클래스들까지 구현했으니 Main클래스에서 실제로 사용해보겠습니다.

 

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

 

Main 클래스에서는 우선 6명의 트러블 해결자를 생성합니다. 그 다음 setNext메소드를 사용해서 일렬로 나열합니다. 그 다음 트러블번호를 0부터 33씩 증가시켜서 발생시킵니다. 실행결과를 보겠습니다.

 

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

 

각각의 트러블번호에 대해서 해결자들이 순서대로 해결하는 모습을 볼 수 있습니다. 사슬에서 앞쪽에 위치한 해결자가 해결하지 못하면 뒤쪽의 해결자에게 떠넘기고 만약 모든 해결자가 해결하지 못하면 "해결하지 못했다"라는 메세지를 보내고 종료합니다.

 

만약 COR패턴을 사용하지 않고 위의 트러블을 해결한다면 어떻게 해야할까요? 그렇다면 각각의 조건을 if-else문으로 나열해야합니다. 다음과 같이 말이죠.

 

class Main {

...

for(Trouble T : list) {
	if(T.getNumber()<100) {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is resolved by [Bob].");
    } else if(T.getNumber()>=100&&T.getNumber()<200) {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is resolved by [Diana].");
    } else if(T.getNumber()>=200&&T.getNumber()<300) {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is resolved by [Fred].");
    } else if (T.getNumber()==429) {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is resolved by [Charlie].");
    } else if (T.getNumber() % 2 == 1) {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is resolved by [Elmo].");
    } else {
    	System.out.println("Trouble [number=" + T.getNumber() + "] is cannot be resolved.");
    }

...

}

 

COR패턴을 사용한 방식보다는 코드가 짧아지지만, 반복되는 코드가 생기고 만약 다른곳에서도 위에 방식으로 사용하다가 해결자가 변경되거나 추가되면 일일히 변경해줘야하는 문제가 생길 수 있습니다. 따라서 조건이 한정적이거나 변경과 확장의 가능성이 없다면 위처럼 if-else문을 사용해서 처리하고, 위의 해결자들이 다른위치에서 빈번하게 사용 될 가능성이 있거나 변경,확장 가능성이 있다면 COR패턴을 사용하는 걸 고려해서 상황에 맞게 적용하면 되겠습니다.

 

장점

  1. 유연성: 책임 연쇄 패턴은 요청 처리의 유연한 구조를 제공합니다. 요청 처리를 담당하는 객체들을 동적으로 추가하거나 제거할 수 있어서 시스템의 유지보수와 확장이 쉽습니다.
  2. 단일 책임 원칙 준수: 이 패턴은 단일 책임 원칙(Single Responsibility Principle)을 따르도록 도와줍니다. 각 처리자 객체는 한 가지 종류의 요청만 처리하며, 이를 통해 코드의 가독성과 유지보수성이 향상됩니다.
  3. 결합도 감소: 클라이언트와 각 처리자 객체 간의 결합도가 낮아집니다. 클라이언트는 어떤 객체가 요청을 처리할지 알 필요가 없으며, 처리자 객체들도 다른 객체들의 존재를 알 필요가 없습니다. 이는 유연한 코드를 작성할 수 있게 합니다.

 

단점

  1. 실행시 요청이 여러 객체를 거치면서 처리될 수 있기 때문에, 디버깅과 테스트가 복잡해질 수 있습니다. 요청이 어디서부터 시작되었는지, 어떤 객체에서 처리되었는지를 파악하기 어려울 수 있습니다.
  2. 책임연쇄로 인해 처리가 지연될 수 있습니다. 다만 이는 트레이드오프 문제로서 요구와 처리자의 관계가 고정적이고 처리 속도가 매우 중요한 경우에는 COR패턴을 사용하지 않는편이 좋습니다.

 

정리

 : 책임 연쇄 패턴은 쉽게 말해서 if-else처리를 클래스로 나누어 관리한다고 생각하면 편합니다. 정수기 필터처럼 구간마다 각각 거르고 걸러서 해결해 준다는 패턴입니다.

반응형

'Develop Story > DesignPattern' 카테고리의 다른 글

Memento Pattern  (0) 2024.04.15
Visitor Pattern  (0) 2024.04.01
Bridge Pattern  (0) 2024.03.25