객체 지향 디자인에서 책임 연쇄 패턴(chain-of-responsibility pattern)은 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들할 수 없는 명령은 다음 처리 객체로 넘겨진다. 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복된다.
표준 책임 연쇄 모델이 변화하면서, 어떤 처리 방식에서는 다양한 방향으로 명령을 보내 책임을 트리 형태로 바꾸는 관제사 역할을 하기도 한다. 몇몇 경우에서는, 처리 객체가 상위의 처리 객체와 명령을 호출하여 작은 파트의 문제를 해결하기 위해 재귀적으로 실행된다. 이 경우 재귀는 명령이 처리되거나 모든 트리가 탐색될때까지 진행되게 된다. XML(파싱되었으나 실행되지 않은) 인터프리터가 한 예이다.
이 패턴은 결합을 느슨하게 하기 위해 고안되었으며 가장 좋은 프로그래밍 사례로 꼽힌다.
예
아래의 자바코드는 로그를 남겨주는 클래스를 보여준다. 각각의 로그 핸들러들은 로그 레벨과 다음 로그 핸들러로 넘겨주는 기능을 가지고 있다.
Writing to stdout: Entering function y.
Writing to stdout: Step1 completed.
Sending via e-mail: Step1 completed.
Writing to stdout: An error has occurred.
Sending via e-mail: An error has occurred.
Writing to stderr: An error has occurred.
이 예가 실제로 어떻게 로그 클래스를 남겨야 하는지에 대한 추천은 아니라는 것을 명심하라.
뿐만 아니라, 완벽한 책임 연쇄 패턴을 구현하기 위해서 로거는 메시지를 넘긴 이후 하위 체인으로 메시지를 전달할 수 없다. 이 예에서 메시지가 처리되는지 여부와 관계없이 하위 체인으로 전달된다.
abstractclassLogger{publicstaticintERR=3;publicstaticintNOTICE=5;publicstaticintDEBUG=7;protectedintmask;// The next element in the chain of responsibilityprotectedLoggernext;publicLoggersetNext(Loggerlog){next=log;returnlog;}publicvoidmessage(Stringmsg,intpriority){if(priority<=mask){writeMessage(msg);}if(next!=null){next.message(msg,priority);}}abstractprotectedvoidwriteMessage(Stringmsg);}classStdoutLoggerextendsLogger{publicStdoutLogger(intmask){this.mask=mask;}protectedvoidwriteMessage(Stringmsg){System.out.println("Writing to stdout: "+msg);}}classEmailLoggerextendsLogger{publicEmailLogger(intmask){this.mask=mask;}protectedvoidwriteMessage(Stringmsg){System.out.println("Sending via email: "+msg);}}classStderrLoggerextendsLogger{publicStderrLogger(intmask){this.mask=mask;}protectedvoidwriteMessage(Stringmsg){System.err.println("Sending to stderr: "+msg);}}publicclassChainOfResponsibilityExample{publicstaticvoidmain(String[]args){// Build the chain of responsibilityLoggerlogger,logger1;logger1=logger=newStdoutLogger(Logger.DEBUG);logger1=logger1.setNext(newEmailLogger(Logger.NOTICE));logger1=logger1.setNext(newStderrLogger(Logger.ERR));// Handled by StdoutLoggerlogger.message("Entering function y.",Logger.DEBUG);// Handled by StdoutLogger and EmailLoggerlogger.message("Step1 completed.",Logger.NOTICE);// Handled by all three loggerslogger.message("An error has occurred.",Logger.ERR);}}
아래는 이 패턴을 Java로 구현한 다른 예제이다.
이 예에서 각각의 위임자와 제한을 각각 다르게 가지고 있다. 매번 역할에 참가하고 있는 사용자는 요청을 수신받고, 상한선과 위임자를 통과한 요청을 보내준다.
아래는 추상 메소드 processRequest를 포함한 PurchasePower 추상 클래스이다.
Four implementations of the abstract class above: Manager, Director, Vice President, President
classManagerPowerextendsPurchasePower{privatefinaldoubleALLOWABLE=10*base;publicvoidprocessRequest(PurchaseRequestrequest){if(request.getAmount()<ALLOWABLE){System.out.println("Manager will approve $"+request.getAmount());}elseif(successor!=null){successor.processRequest(request);}}}classDirectorPPowerextendsPurchasePower{privatefinaldoubleALLOWABLE=20*base;publicvoidprocessRequest(PurchaseRequestrequest){if(request.getAmount()<ALLOWABLE){System.out.println("Director will approve $"+request.getAmount());}elseif(successor!=null){successor.processRequest(request);}}}classVicePresidentPPowerextendsPurchasePower{privatefinaldoubleALLOWABLE=40*base;publicvoidprocessRequest(PurchaseRequestrequest){if(request.getAmount()<ALLOWABLE){System.out.println("Vice President will approve $"+request.getAmount());}elseif(successor!=null){successor.processRequest(request);}}}classPresidentPPowerextendsPurchasePower{privatefinaldoubleALLOWABLE=60*base;publicvoidprocessRequest(PurchaseRequestrequest){if(request.getAmount()<ALLOWABLE){System.out.println("President will approve $"+request.getAmount());}else{System.out.println("Your request for $"+request.getAmount()+" needs a board meeting!");}}}
The PurchaseRequest class with its Getter methods which keeps the request data in this example.
And here a usage example, the successors are set like this: Manager -> Director -> Vice President -> President
classCheckAuthority{publicstaticvoidmain(String[]args){ManagerPPowermanager=newManagerPPower();DirectorPPowerdirector=newDirectorPPower();VicePresidentPPowervp=newVicePresidentPPower();PresidentPPowerpresident=newPresidentPPower();manager.setSuccessor(director);director.setSuccessor(vp);vp.setSuccessor(president);// Press Ctrl+C to end.try{while(true){System.out.println("Enter the amount to check who should approve your expenditure.");System.out.print(">");doubled=Double.parseDouble(newBufferedReader(newInputStreamReader(System.in)).readLine());manager.processRequest(newPurchaseRequest(0,d,"General"));}}catch(Exceptione){System.exit(1);}}}