본문 바로가기

Design Patterns/영문 위키(Wikipedia)

Chain-of-responsibility pattern

먼저 이 글은 영문 위키의 글을 번역한 글임을 알려드립니다.

영어 실력이 부족한 관계로 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern



행위 패턴 - Chain-of-responsibility




객체지향 프로그래밍(Object Oriented Design)에서 chain-of-responsibility 패턴command objects의 소스와 일련의 processing objects로 구성되는 디자인 패턴이다. 각각의 processing object는 그것이 다룰 수 있는 command objects의 타입을 정의하는 로직을 포함한다.; 나머지 것들은 체인(chain)의 다음processing object에게 전달된다. 메카니즘은 체인의 마지막에 새로운 processing objects를 추가하는 것에도 존재한다. 


기본 chain-of-responsibility 모델의 변경에서는, 일부 핸들러(handlers)가 dispatchers의 역할을 할 수도 있다. 여기에서 dispatcher는 책임의 나무(tree of responsibility)의 형태를 보이게끔 다양한 방향으로 명령들을(commands) 보낼 수 있다. 몇몇 상황에서는 이런 일이 재귀적으로 발생할 수 있는데, processing objects가 더 높은 레벨의(higher-up) processing objects를 호출하고 이 때의 commands는 문제의 더 작은 일부분을 풀기위한 시도이다.; 이런 상황에서 반복(recursion)은 command가 처리될때까지 계속되거나, 전체 트리(tree)를 다 돌았을때(explored, 탐험했을때) 끝난다. XML 인터프리터는 아마도 이러한 방법으로 동작할 것이다.


이 패턴은 programming best practice의 하나로 간주되는 느슨한 결합(lose coupling)의 아이디어에서 나온 것이다.

예 Example

아래의 코드는 로그를 기록하는 클래스를 예로 들어 이 패턴에 대해 설명한다. 각각의 로그 핸들러는 현재 로그 레벨에서 어떤 행동(action)을 취할지 결정하고 다음 로그 핸들러에게 메세지를 전달한다. 이 예는 어떻게 로그 클래스를 작성하는지에 대한 추천하는 것은 아니지 주의해라.


또한 chain of responsiblity 패턴의 순수한('pure') 구현에서 로거(logger)는 메세지를 처리하고 난 뒤에 다음 체인으로 책임을 전달하지 않을지도 모른다는 것을 알아둬라. 이 예에서는 처리가 되었든 안되었든 상관안하고 메세지는 체인으로 전달될 것이다.

Java

package chainofresp;
 
abstract class Logger {
    public static int ERR = 3;
    public static int NOTICE = 5;
    public static int DEBUG = 7;
    protected int mask;
 
    // The next element in the chain of responsibility
    protected Logger next;
 
    public Logger setNext(Logger log) {
        next = log;
        return log;
    }
 
    public void message(String msg, int priority) {
        if (priority <= mask) {
            writeMessage(msg);
        }
        if (next != null) {
            next.message(msg, priority);
        }
    }
 
    abstract protected void writeMessage(String msg);
}
 
class StdoutLogger extends Logger {
    public StdoutLogger(int mask) { 
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.out.println("Writing to stdout: " + msg);
    }
}
 
 
class EmailLogger extends Logger {
    public EmailLogger(int mask) {
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.out.println("Sending via email: " + msg);
    }
}
 
class StderrLogger extends Logger {
    public StderrLogger(int mask) {
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.err.println("Sending to stderr: " + msg);
    }
}
 
public class ChainOfResponsibilityExample {
    public static void main(String[] args) {
        // Build the chain of responsibility
        Logger logger, logger1,logger2;
        logger = new StdoutLogger(Logger.DEBUG);
        logger1 = logger.setNext(new EmailLogger(Logger.NOTICE));
        logger2 = logger1.setNext(new StderrLogger(Logger.ERR));
 
        // Handled by StdoutLogger
        logger.message("Entering function y.", Logger.DEBUG);
 
        // Handled by StdoutLogger and EmailLogger
        logger.message("Step1 completed.", Logger.NOTICE);
 
        // Handled by all three loggers
        logger.message("An error has occurred.", Logger.ERR);
    }
}
/*
The output is:
   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.
*/


C#

using System;
using System.IO;
 
namespace ChainOfResponsibility
{
    public enum LogLevel
    {
        Info=1,
        Debug=2,
        Warning=4,
        Error=8,
        FunctionalMessage=16,
        FunctionalError=32,
        All = 63
    }
 
    /// 
    /// Abstract Handler in chain of responsibility Pattern
    /// 
    public abstract class Logger
    {
        protected LogLevel logMask;
 
        // The next Handler in the chain
        protected Logger next;
 
        public Logger(LogLevel mask)
        {
            this.logMask = mask;
        }
 
        /// 
        /// Sets the Next logger to make a list/chain of Handlers
        /// 
        public Logger SetNext(Logger nextlogger)
        {
            next = nextlogger;
            return nextlogger;
        }
 
        public void Message(string msg, LogLevel severity)
        {
            if ((severity & logMask) != 0)
            {
                WriteMessage(msg);
            }
            if (next != null)
            {
                next.Message(msg, severity);
            }
        }
 
        abstract protected void WriteMessage(string msg);
    }
 
    public class ConsoleLogger : Logger
    {
        public ConsoleLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            Console.WriteLine("Writing to console: " + msg);
        }
    }
 
    public class EmailLogger : Logger
    {
        public EmailLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            //Placeholder for mail send logic, usually the email configurations are saved in config file.
            Console.WriteLine("Sending via email: " + msg);
        }
    }
 
    class FileLogger : Logger
    {
        public FileLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            //Placeholder for File writing logic
            Console.WriteLine("Writing to Log File: " + msg);
        }
    }
 
    public class Program
    {
        public static void Main(string[] args)
        {
            // Build the chain of responsibility
            Logger logger, logger1, logger2;
            logger1 = logger = new ConsoleLogger(LogLevel.All);
            logger1 = logger1.SetNext(new EmailLogger(LogLevel.FunctionalMessage | LogLevel.FunctionalError));
            logger2 = logger1.SetNext(new FileLogger(LogLevel.Warning | LogLevel.Error));
 
            // Handled by ConsoleLogger
            logger.Message("Entering function ProcessOrder().", LogLevel.Debug);
            logger.Message("Order record retrieved.", LogLevel.Info);
 
            // Handled by ConsoleLogger and FileLogger
            logger.Message("Customer Address details missing in Branch DataBase.", LogLevel.Warning);
            logger.Message("Customer Address details missing in Organization DataBase.", LogLevel.Error);
 
            // Handled by ConsoleLogger and EmailLogger
            logger.Message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FunctionalError);
 
            // Handled by ConsoleLogger and EmailLogger
            logger.Message("Order Dispatched.", LogLevel.FunctionalMessage);
        }
    }
}
 
/* Output
Writing to console: Entering function ProcessOrder().
Writing to console: Order record retrieved.
Writing to console: Customer Address details missing in Branch DataBase.
Writing to Log File: Customer Address details missing in Branch DataBase.
Writing to console: Customer Address details missing in Organization DataBase.
Writing to Log File: Customer Address details missing in Organization DataBase.
Writing to console: Unable to Process Order ORD1 Dated D1 For Customer C1.
Sending via email: Unable to Process Order ORD1 Dated D1 For Customer C1.
Writing to console: Order Dispatched.
Sending via email: Order Dispatched.
*/

또 다른 예 Another Example

아래는 자바로 작성한 또 다른 예이다. 이 예에서 우리는 다른 역할들을 가지고 있다. 각각의 역할들은 정해진 구매 한계치(purchasing limit)와 하나의 후계자(successor)를 가진다.(여기에서 후계자는 앞에서 나왔던 chain이라고 생각하시면 됩니다. 책임을 전달한 객체를 의미하죠.) 역할을 담당해야하는 유저는 구매 요청을 받는데 이 구매 요청이 그의 한계를 초과하는 경우에, 요청은 그의 후계자로 전달된다.


processRequest 추상 메서드를 가지고 있는 PurchasePower 추상 클래스

abstract class PurchasePower {
    protected final double base = 500;
    protected PurchasePower successor;
 
    public void setSuccessor(PurchasePower successor) {
        this.successor = successor;
    }
 
    abstract public void processRequest(PurchaseRequest request);
}


위의 추상 클래스를 상속받는 4개의 구현들 : Manager, Director, Vice President, President

class ManagerPPower extends PurchasePower {
    private final double ALLOWABLE = 10 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Manager will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class DirectorPPower extends PurchasePower {
    private final double ALLOWABLE = 20 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Director will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class VicePresidentPPower extends PurchasePower {
    private final double ALLOWABLE = 40 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Vice President will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class PresidentPPower extends PurchasePower {
    private final double ALLOWABLE = 60 * base;
 
    public void processRequest(PurchaseRequest request) {
        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!");
        }
    }
}


아래의 코드는 이 예에서 요청 데이터를 유지하는 PurchaseRequest 클래스를 정의한다.

class PurchaseRequest {
    private int number;
    private double amount;
    private String purpose;
 
    public PurchaseRequest(int number, double amount, String purpose) {
        this.number = number;
        this.amount = amount;
        this.purpose = purpose;
    }
 
    public double getAmount() {
        return amount;
    }
    public void setAmount(double amt)  {
        amount = amt;
    }
 
    public String getPurpose() {
        return purpose;
    }
    public void setPurpose(String reason) {
        purpose = reason;
    }
 
    public int getNumber(){
        return number;
    }
    public void setNumber(int num) {
        number = num;
    }   
}


아래의 사용 예에서 후계자들(successors)은 뒤에 나오는 순서대로이다. : Manager->Director->Vice President->President

class CheckAuthority {
    public static void main(String[] args) {
        ManagerPPower manager = new ManagerPPower();
        DirectorPPower director = new DirectorPPower();
        VicePresidentPPower vp = new VicePresidentPPower();
        PresidentPPower president = new PresidentPPower();
        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(">");
                double d = Double.parseDouble(new BufferedReader(new InputStreamReader(System.in)).readLine());
                manager.processRequest(new PurchaseRequest(0, d, "General"));
           }
        } catch(Exception e) {
            System.exit(1);
        }  
    }
}


'Design Patterns > 영문 위키(Wikipedia)' 카테고리의 다른 글

Interpreter pattern  (0) 2012.06.15
Command Pattern  (0) 2012.06.15
Proxy pattern (프록시 패턴)  (0) 2012.06.06
Flyweight pattern (플라이웨이트 패턴)  (0) 2012.06.05
Facade pattern (퍼사드 패턴)  (0) 2012.06.04