#責任鏈模式(Chain-of-responsibility pattern)
##簡介
責任鏈模式在面向對象程式設計里是一種軟件設計模式,它包含了一些命令對象和一系列的處理對象。每一個處理對象決定它能處理哪些命令對象,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法。
以下的日志類(logging)例子演示了該模式。 每一個logging handler首先決定是否需要在該層做處理,然后將控制傳遞到下一個logging handler。程序的輸出是:
```
Writing to debug output: Entering function y.
Writing to debug output: Step1 completed.
Sending via e-mail: Step1 completed.
Writing to debug output: An error has occurred.
Sending via e-mail: An error has occurred.
Writing to stderr: An error has occurred.
```
注意:該例子不是日志類的推薦實現方式。
同時,需要注意的是,通常在責任鏈模式的實現中,如果在某一層已經處理了這個logger,那么這個logger就不會傳遞下去。在我們這個例子中,消息會一直傳遞到最底層不管它是否已經被處理。
```
import java.util.*;
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 l)
{
next = l;
return this;
}
public final void message( String msg, int priority )
{
if ( priority <= mask )
{
writeMessage( msg );
if ( next != null )
{
next.message( msg, priority );
}
}
}
protected abstract void writeMessage( String msg );
}
class StdoutLogger extends Logger
{
public StdoutLogger( int mask ) { this.mask = mask; }
protected void writeMessage( String msg )
{
System.out.println( "Writting 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.out.println( "Sending to stderr: " + msg );
}
}
public class ChainOfResponsibilityExample
{
public static void main( String[] args )
{
// Build the chain of responsibility
Logger l = new StdoutLogger( Logger.DEBUG).setNext(
new EmailLogger( Logger.NOTICE ).setNext(
new StderrLogger( Logger.ERR ) ) );
// Handled by StdoutLogger
l.message( "Entering function y.", Logger.DEBUG );
// Handled by StdoutLogger and EmailLogger
l.message( "Step1 completed.", Logger.NOTICE );
// Handled by all three loggers
l.message( "An error has occurred.", Logger.ERR );
}
}
```
##加薪代碼初步
```
//申請
class Request
{
//申請類別
private string requestType;
public string RequestType
{
get {return requestType;}
set {requestType = value;}
}
//申請內容
private string requestContent;
public string RequestContent
{
get {return RequestContent;}
set {RequestContent = value;}
}
//數量
private int number;
public int Number
{
get {return number;}
set {number = value;}
}
}
```
```
//管理者
class Manager
{
protected string name;
public Manager(string name)
{
this.name = name;
}
//得到結果
public void GetResult(string managerLevel, Request request)
{
if (request.RequestType=="經理") {
if (request.RequestType == "請假" && request.Number <= 2)
{
Console.WriteLine("被批準");
} else
{
Console.WriteLine("我無權處理");
}
} else if (request.RequestType=="總監") {
if (request.RequestType == "請假" && request.Number <= 5)
{
Console.WriteLine("被批準");
} else
{
Console.WriteLine("我無權處理");
}
} else if (request.RequestType=="總經理") {
if (request.RequestType == "請假“) {
Console.WriteLine("被批準");
} else if (request.RequestType == "加薪" && request.Number <= 500) {
Console.WriteLine("被批準");
} else if (request.RequestType == "加薪" && request.Number > 500) {
Console.WriteLine("再說");
}
}
}
}
```
客戶端代碼如下
```
static void Main(string[] args)
{
Manager jinli = new Manager("金利");
Manager zongjian = new Manager("宗劍");
Manager zhongjingli = new Manager("鐘精勵");
Request request = new Reuqest();
request.RequestType = "加薪";
request.RequestContent = "小菜請求加薪";
request.Number = 1000;
Request request2 = new Reuqest();
request2.RequestType = "請假";
request2.RequestContent = "小菜請假";
request2.Number = 3;
jinli.GetResult("經理",request2);
zongjian.GetResult("總監",request2);
zhongjingli.GetResult("總經理",request2);
Console.Read();
}
```
代碼評判:'管理者'類里面的'結果'方法比較長,加上有太多的分支判斷,這種設計很不好。而且會不會增加其它的管理類別,比如說項目經理,部門經理,那就意味者需要去更改這個類,這個類承擔了太多的責任,這違背了哪些設計原則?
類有太多的責任,這違背了單一職責原則,增加新的管理類別,需要修改這個類,違背了開放-封閉原則。
子類化加多太改善。
職責鏈模式:使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系,將這個對象連成一條鏈,并沿著個鏈傳遞請求,直到有一個對象處理為止。
###加薪代碼重構
```
//管理者
abstract class Manager
{
protected Manager superior;
public Manager(string name)
{
this.name = name;
}
//設置管理者的上級
public void setSuperior(Manager superior)
{
this.superior = superior;
}
}
```
經理類就可以去繼承這個'管理者'類,只需重寫'申請要求'的方法就可以了。
```
class CommonManager : Manager
{
public CommonManager(string name) :base(name)
{}
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假" && request.Number <= 2)
{
Console.WriteLine("被批準");
} else
{
if (superior != null) superior.RequestApplications(request);
}
}
}
```
總監類同樣繼承'管理者類'。
```
//總監
class Majordomo :Manager
{
public Major(string name): base(name)
{}
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假" && request.Number <= 2)
{
Console.WriteLine("被批準");
} else
{
if (superior != null) superior.RequestApplications(request);
}
}
}
```
總經理類的權限就是全部都需要處理。
```
//總經理
class GeneralManager :Manager
{
public GeneralManager(string name): base(name)
{}
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假")
{
Console.WriteLine("被批準");
} else if (request.RequestType == "加薪" && request.Number <= 500)
{
Console.WriteLine("被批準");
} else if (request.RequestType == "加薪" && request.Number > 500)
{
Console.WriteLine("再說吧");
}
}
}
```
由于我們把你原來的一個'管理者'類改成了一個抽象類和三個具體類,此時類之間的靈活性就大大的增加了,如果我們需要擴展新的管理類別,只需要增加子類就可以。當然,還有一個關鍵,那就是客戶端如何編寫。
```
static void Main(string[] args)
{
CommonManager jinli = new CommonManger("金利");
Majordomo zongjian = new Majordomo("宗建");
GeneralManager zhongjingli = newGeneralManager("鐘精利");
//設置上級
jinli.SetSuperior(zongjian);
zongjian.SetSuperior(zhongjingli);
Request request = new Request();
request.RequestType = "請假";
request.RequestContent = "小菜請假";
request.Number = 1;
//客戶端的申請都是有‘經理’發起,但是實際誰來決策都是由具體管理類處理,客戶端不知道
jinli.requestApplications(request);
Request request1 = new Request();
request1.RequestType = "請假";
request1.RequestContent = "小菜請假";
request1.Number = 4;
jinli.requestApplications(request1);
Request request2 = new Request();
request2.RequestType = "請求加薪";
request2.RequestContent = "小菜請加薪";
request2.Number = 400;
jinli.requestApplications(request2);
Console.Read();
}
```
責任鏈模式是一種對象的行為模式【GOF95】。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。
###門框夾核桃
職責鏈模式想要做到的事情其實就是把多個函數鏈起來調用。
該模式提出的時候FP并不如今日盛行,其作者選用類來包裝需要被鏈接的多個函數,這無可厚非。
無論是class,還是function,都是為程序員提供抽象的手段。當我們想要鏈接的東西就是多個function,選擇直接用function而非class就會顯得更加自然,也更加輕量且合適。
```
object Loggers {
val ERR = 3
val NOTICE = 5
val DEBUG = 7
case class Event(message: String, priority: Int)
type Logger = Event => Event
def stdOutLogger(mask: Int): Logger = event => handleEvent(event, mask) {
println(s"Writing to stdout: ${event.message}")
}
def emailLogger(mask: Int): Logger = event => handleEvent(event, mask) {
println(s"Sending via e-mail: ${event.message}")
}
def stdErrLogger(mask: Int): Logger = event => handleEvent(event, mask) {
System.err.println(s"Sending to stderr: ${event.message}")
}
private def handleEvent(event: Event, mask: Int)(handler: => Unit) = {
if (event.priority <= mask) handler
event
}
}
```
三個log的的等級ERR,NOTICE和DEBUG和之前Java的實現是一樣的。
一個case class Event,用來包裹需要被log的事件。
type Logger則是聲明了一個函數簽名,凡是符合這個簽名的函數都可以作為logger被使用。
然后便是三個函數實現,它們將mask通過閉包封進函數內。這三個函數共同依賴一個私有handleEvent函數,其作用和Java代碼中的message類似,判斷mask和正在發生的事件之間優先級大小關系,并以此決定當前logger是否需要處理該事件。
哎?等一下,這個是職責鏈模式啊,那個啥,鏈在哪兒呢?
```
object ChainRunner {
import chain.Loggers._
def main(args: Array[String]) {
val chain = stdOutLogger(DEBUG) andThen emailLogger(NOTICE) andThen stdErrLogger(ERR)
chain(Event("Entering function y.", DEBUG))
chain(Event("Step1 completed.", NOTICE))
chain(Event("An error has occurred.", ERR))
}
}
```
以上代碼中的andThen就可以把三個logger鏈在一起。