设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型)

1.概述

       你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决。不能解决就推卸给另外个一个部门(对象)。至于究竟谁来解决问题呢?政府部门就是为了能够避免屁民的请求与官员之间耦合在一起,让多个(部门)对象都有可能接收请求,将这些(部门)对象连接成一条链,而且沿着这条链传递请求。直到有(部门)对象处理它为止。

样例1:js的事件浮升机制


样例2:


2.问题

假设有多个对象都有可能接受请求,怎样避免避免请求发送者与接收者耦合在一起呢?

3.解决方式

职责链模式(Chain of Responsibility)使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

Avoid coupling the sender of a request to itsreceiver by giving morethan one objecta chance to handle the request.Chain the receiving objects andpassthe request along the chain until an object handles it. 

1)在职责链模式里。非常多对象由每个对象对其下家的引用而连接起来形成一条链

2)请求在这条链上传递,直到链上的某一个对象处理此请求为止。

3)发出这个请求的client并不知道链上的哪一个对象终于处理这个请求,这使得系统能够在不影响client的情况下动态地又一次组织链和分配责任

4.适用性

在下面条件下使用Responsibility 链:

• 有多个的对象能够处理一个请求,哪个对象处理该请求执行时刻自己主动确定。

• 你想在不明白指定接收者的情况下,向多个对象中的一个提交一个请求。

•可动态指定一组对象处理请求

5.结构

        

一个典型的对象结构可能例如以下图所看到的:


6. 模式的组成

抽象处理者角色(Handler:Approver):定义一个处理请求的接口,和一个后继连接(可选)

详细处理者角色(ConcreteHandler:President):处理它所负责的请求。能够訪问后继者,假设能够处理请求则处理,否则将该请求转给他的后继者。

客户类(Client):向一个链上的详细处理者ConcreteHandler对象提交请求。

7. 效果

Responsibility 链有下列长处和缺点( l i a b i l i t i e s ) :

职责链模式的长处:

1 ) 减少耦合度 :该模式使得一个对象无需知道是其它哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。

接收者和发送者都没有对方的明白的信息,且链中的对象不需知道链的结构。

2) 职责链可简化对象的相互连接 :    结果是。职责链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用。而不需保持它全部的候选接受者的引用。

3) 增强了给对象指派职责( R e s p o n s i b i l i t y )的灵活性 :当在对象中分派职责时,职责链给你很多其它的灵活性。你能够通过在执行时刻对该链进行动态的添加或改动来添加或改变处理一个请求的那些职责。你能够将这样的机制与静态的特例化处理对象的继承机制结合起来使用。

4)添加新的请求处理类非常方便

职责链模式的缺点:
1) • 不能保证请求一定被接收。既然一个请求没有明白的接收者。那么就不能保证它一定会被处理 —该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。

2) • 系统性能将受到一定影响,并且在进行代码调试时不太方便。可能会造成循环调用。

8. 纯与不纯的职责链模式

纯的职责链模式:一个详细处理者角色处理仅仅能对请求作出两种行为中的一个:一个是自己处理(承担责任),还有一个是把责任推给下家。不同意出现某一个详细处理者对象在承担了一部分责任后又将责任向下传的情况。

请求在责任链中必须被处理。不能出现无果而终的结局。

反之就是不纯的职责链模式。  
在一个纯的职责链模式里面,一个请求必须被某一个处理者对象所接收。在一个不纯的职责链模式里面,一个请求能够终于不被不论什么接收端对象所接收。

9.实现

我们先来看不纯的职责模式:

假如在公司里,

假设你的请假时间小于0.5天。那么仅仅须要向leader打声招呼就OK了。
假设0.5<请假天数<=3天。须要先leader打声招呼。要不然leader不知你跑哪里,然后部门经理直接签字。
假设3<请假天数 天,须要先leader打声招呼,然后到部门经理签字,最好总经经理确认签字。

当你看到这情况后你心里是不是已经有了自己的想法了?写一系列的if语句来一条条的推断.但这种写法尽管能够实现眼下的需求,可假设当流程改了呢?我请假超过3天。告诉leader和总经理签字就能够。那你又得一步一步改动程序。假设if语句的条数发生变化的话我们还必须在代码中加入必要的if推断,这对于程序的维护来说是相当麻烦的.假设我们使用职责链模式的话就能够相当简单了.

这个样例就是个list。也是个不纯的职责链,由于每一个对象可能处理一部分后,就须要传给下个对象来处理。

[php] view plain copy
 
 
  1. <?php  
  2.   
  3. /** 
  4.  * 增加在公司里。假设你的请假时间小于0.5天,那么仅仅须要向leader打声招呼就OK了。 
  5.   假设0.5<请假天数<=3天。须要先leader打声招呼。要不然leader不知你跑哪里,然后部门经理直接签字。

     

  6.   假设3<请假天数 天,须要先leader打声招呼。然后到部门经理签字,最好总经经理确认签字。 
  7.   这样就是个list。也是个不纯的职责链。由于每一个对象可能处理一部分后,就须要传给下个对象来处理。 
  8.    
  9.  */  
  10.   
  11. /** 
  12. * 纯职责链模式  
  13.  
  14. * 为解除请求的发送者和接收者之间的耦合,而使用多个对象都用机会处理这个请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它  
  15. * @author guisu 
  16.  
  17. */   
  18. /** 
  19.  * 抽象处理者角色(Handler:Approver):定义一个处理请求的接口,和一个后继连接(可选) 
  20.  * 
  21.  */  
  22. abstract class Handler  
  23. {  
  24.     protected $_handler = null;  
  25.     protected $_handlerName = null;  
  26.       
  27.     public function setSuccessor($handler)  
  28.     {  
  29.         $this->_handler = $handler;  
  30.     }  
  31.       
  32.     protected  function _success($request)  
  33.     {  
  34.         echo $request->getName(), '\' request was passed  <br/>';  
  35.         return true;  
  36.     }  
  37.     abstract function handleRequest($request);  
  38. }  
  39. /** 
  40.  * 详细处理者角色(ConcreteHandler:President):处理它所负责的请求。能够訪问后继者,假设能够处理请求则处理。否则将该请求转给他的后继者。 
  41.  * 
  42.  */  
  43. class ConcreteHandlerLeader extends Handler  
  44. {  
  45.     function __construct($handlerName){  
  46.         $this->_handlerName = $handlerName;  
  47.     }  
  48.     public function handleRequest($request)  
  49.     {  
  50.         echo $this->_handlerName, ' was known <br/>';//已经跟leader招呼了  
  51.         if($request->getDay() < 0.5) {  
  52.             return $this->_success($request);  
  53.         }   
  54.         if ($this->_handler instanceof Handler) {  
  55.             return $this->_handler->handleRequest($request);  
  56.         }  
  57.     }  
  58. }  
  59. /** 
  60.  * Manager 
  61.  * 
  62.  */  
  63. class ConcreteHandlerManager extends Handler  
  64. {  
  65.     function __construct($handlerName){  
  66.         $this->_handlerName = $handlerName;  
  67.     }  
  68.       
  69.     public function handleRequest($request)  
  70.     {  
  71.         echo $this->_handlerName, " was signed <br/>";//部门经理签字  
  72.         if$request->getDay() > 0.5 && $request->getDay()<=3) {  
  73.             return $this->_success($request);  
  74.         }   
  75.         if ($this->_handler instanceof Handler) {  
  76.             return $this->_handler->handleRequest($request);  
  77.         }  
  78.     }  
  79. }  
  80. class ConcreteHandlerGeneralManager extends Handler  
  81. {  
  82.     function __construct($handlerName){  
  83.         $this->_handlerName = $handlerName;  
  84.     }  
  85.       
  86.     public function handleRequest($request)  
  87.     {  
  88.         echo $this->_handlerName, " was signed <br/>";//总经理签字  
  89.         if(3 < $request->getDay()){  
  90.             return $this->_success($request);  
  91.         }  
  92.         if ($this->_handler instanceof Handler) {  
  93.             return $this->_handler->handleRequest($request);  
  94.         }  
  95.     }  
  96. }  
  97. /** 
  98.  * 请假申请 
  99.  * 
  100.  */  
  101. class   Request  
  102. {  
  103.     private $_name;  
  104.     private $_day;  
  105.     private $_reason;  
  106.   
  107.     function __construct($name''$day= 0, $reason = ''){  
  108.         $this->_name = $name;  
  109.         $this->_day = $day;  
  110.         $this->_reason = $reason;  
  111.     }  
  112.       
  113.     public function setName($name){  
  114.         $this->_name = $name;  
  115.     }  
  116.     public function getName(){  
  117.         return  $this->_name;  
  118.     }  
  119.       
  120.     public function setDay($day){  
  121.         $this->_day = $day;  
  122.     }  
  123.     public function getDay(){  
  124.         return  $this->_day ;  
  125.     }  
  126.       
  127.     public function setReason($reason ){  
  128.          $this->_reason = $reason;  
  129.     }  
  130.     public function getReason( ){  
  131.         return  $this->_reason;  
  132.     }  
  133. }  
  134.   
  135.   
  136. class client{  
  137.       
  138.     /** 
  139.      *流程1:leader-> manager ->generalManager 
  140.      * 
  141.      */  
  142.     static function main(){  
  143.           
  144.         $leader = new ConcreteHandlerLeader('$leader');  
  145.         $manager = new ConcreteHandlerManager('$manager');  
  146.         $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
  147.           
  148.         //请求实例  
  149.         $request = new Request('guisu',4,'歇息' );  
  150.           
  151.         $leader->setSuccessor($manager);  
  152.         $manager->setSuccessor($generalManager);  
  153.         $result =  $leader->handleRequest($request);  
  154.     }  
  155.       
  156.     /** 
  157.      * 流程2 : 
  158.      * leader ->generalManager 
  159.      */  
  160.     static function main2(){  
  161.         //签字列表  
  162.         $leader = new ConcreteHandlerLeader('$leader');  
  163.         $manager = new ConcreteHandlerManager('$manager');  
  164.         $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
  165.           
  166.         //请求实例  
  167.         $request = new Request('guisu',3,'歇息' );  
  168.         $leader->setSuccessor($generalManager);  
  169.         $result = $leader->handleRequest($request);  
  170.     }  
  171.       
  172.     /** 
  173.      * 流程3 :假设leader不在,那么全然能够写这种代码 
  174.      * manager ->generalManager 
  175.      */  
  176.     static function main3(){  
  177.         //签字列表  
  178.         $leader = new ConcreteHandlerLeader('$leader');  
  179.         $manager = new ConcreteHandlerManager('$manager');  
  180.         $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
  181.           
  182.         //请求实例  
  183.         $request = new Request('guisu',0.1,'歇息' );  
  184.         $leader->setSuccessor($manager);  
  185.         $manager->setSuccessor($generalManager);  
  186.         $result = $manager->handleRequest($request);  
  187.     }  
  188. }  
  189.   
  190.   
  191.   
  192. client::main3();  

对于怎么维护职责的链子。《设计模式》只说自己去实现。能够使用list或者map的形式。

我们吧把职责链模式应用到面向过程编程。而不是对象。比如:

[php] view plain copy
 
 
  1.   个税起征点3500元  
  2.  级数   全月应纳税所得额            税率(%)  
  3. 1    不超过1500元的                3  
  4. 2    超过1500元至4500元的部分      10  
  5. 3    超过4500元至9000元的部分      20  
  6. 4    超过9000元至35000元的部分     25  
  7. 5    超过35000元至55000元的部分    30  
  8. 6    超过55000元至80000元的部分    35  
  9. 7    超过80000元的部分              45  

我们能够不必使用那么多的if和elseif语句推断。

我们仅仅要配置$taxs数组就能够了,而不用改动程序。

[php] view plain copy
 
 
  1. <?php  
  2.   
  3. /** 
  4.  * 个税起征点3500元 
  5. 级数   全月应纳税所得额            税率(%) 
  6.    1    不超过1500元的                3 
  7.    2    超过1500元至4500元的部分      10 
  8.    3    超过4500元至9000元的部分      20 
  9.    4    超过9000元至35000元的部分     25 
  10.    5    超过35000元至55000元的部分    30 
  11.    6    超过55000元至80000元的部分    35 
  12.    7    超过80000元的部分             45 
  13. */  
  14. /** 
  15.  * 这个样例还没有扣除社保公积金等 
  16.  */  
  17. //收入  
  18. $income = 84000;  
  19. //税率  
  20. $taxs[1] = array(1500, 0.03);  
  21. $taxs[2] = array(4500, 0.1);  
  22. $taxs[3] = array(9000, 0.2);  
  23. $taxs[4] = array(35000, 0.25);  
  24. $taxs[5] = array(55000, 0.30);  
  25. $taxs[6] = array(80000, 0.35);  
  26. $taxs[7] = array(1000000000, 0.45);  
  27.   
  28. /** 
  29.  * 计算税率 
  30.  * 
  31.  * @param int $income 
  32.  * @return int 
  33.  */  
  34. function compTax($income){  
  35.     global $taxs;  
  36.     //个税起点  
  37.     $taxStart  = 3500;  
  38.     $incomeTax = $income > $taxStart ?

    ($income - $taxStart) : 0;  

  39.     $flag = false;  
  40.     foreach ($taxs as $values) {  
  41.         if ($incomeTax < $values[0]  ) {  
  42.             $compTax = $incomeTax * $values[1];  
  43.             break;  
  44.         }else{  
  45.             continue;  
  46.         }  
  47.     }  
  48.     return $compTax;  
  49. }  
  50.   
  51. echo compTax($income);  
  52. echo '-------------------<br/>';  
假设推断的条件非常多,也就是数组$taxs非常庞大。那么我们能够使用折半查找的方式:

[php] view plain copy
 
 
  1. <?

    php  

  2.   
  3. /** 
  4.  * 个税起征点3500元 
  5. 级数   全月应纳税所得额            税率(%) 
  6.    1    不超过1500元的                3 
  7.    2    超过1500元至4500元的部分      10 
  8.    3    超过4500元至9000元的部分      20 
  9.    4    超过9000元至35000元的部分     25 
  10.    5    超过35000元至55000元的部分    30 
  11.    6    超过55000元至80000元的部分    35 
  12.    7    超过80000元的部分             45 
  13. */  
  14. /** 
  15.  * 这个样例还没有扣除社保公积金等 
  16.  */  
  17. //收入  
  18. $income = 84000;  
  19. //税率  
  20. $taxs[1] = array(1500, 0.03);  
  21. $taxs[2] = array(4500, 0.1);  
  22. $taxs[3] = array(9000, 0.2);  
  23. $taxs[4] = array(35000, 0.25);  
  24. $taxs[5] = array(55000, 0.30);  
  25. $taxs[6] = array(80000, 0.35);  
  26. $taxs[7] = array(1000000000, 0.45);  
  27.   
  28. /** 
  29.  * 优化计算税率:使用折半查找法,有效缩短时间复杂度 
  30.  */  
  31.   
  32. /** 
  33.  * 优化计算税率:折半查找法 
  34.  * 
  35.  * @param int $income 
  36.  * @return int 
  37.  */  
  38. function optimizeCompTax($income){  
  39.     //个税起点  
  40.     global $taxs;  
  41.     $taxStart  = 3500;  
  42.     $incomeTax = $income > $taxStart ?

    ($income - $taxStart) : 0;  

  43.     $key = bSearch($taxs$incomeTax, 1);  
  44.     return $incomeTax * $taxs[$key][1];  
  45. }  
  46.   
  47. /** 
  48.  *  
  49.  * 折半查找法 
  50.  * @param unknown_type $taxs 
  51.  * @param unknown_type $incomeTax 
  52.  * @return unknown 
  53.  */  
  54. function bSearch($taxs$incomeTax$start = 0){  
  55.       
  56.     $incomeTax = intval($incomeTax);  
  57.     ksort($taxs);  
  58.     foreach ($taxs as $key => $values) {  
  59.         $low = $key;  
  60.         break;  
  61.     }  
  62.     if ($incomeTax <=0 ) {  
  63.         return $low;  
  64.     }  
  65.     $high = count($taxs) + $low -1;  
  66.     while  ( $low < $high){  
  67.         $mid = intval(($low + $high)/2) ;  
  68.         if ( $incomeTax < $taxs[$mid][0] ) {//后半区找  
  69.             $high = $mid;  
  70.         } else { //前半区找  
  71.             $low = $mid ;  
  72.         }  
  73.         /** 
  74.          * 因为这个不是全然折半查找 
  75.          * 仅仅有两个元素的时候,须要推断 
  76.          */  
  77.         if (($high - $low) ==1) {  
  78.             if ( $incomeTax > $taxs[$low][0] ) {  
  79.                 $key = $high;  
  80.             } else{  
  81.                 $key = $low;  
  82.             }  
  83.             break;  
  84.         }  
  85.     }  
  86.     return $key;  
  87. }  
  88. echo optimizeCompTax($income);  

10.与其它相关模式

职责链常与Composite组合模式一起使用。

这样的情况下,一个构件的父构件可作为它的后继

11.总结

      
职责链模式里,非常多对象由每个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递。直到链上的某一个对象决定处理此请求。发出这个请求的client并不知道链上的哪一个对象终于处理这个请求。这使得系统能够在不影响client的情况下动态地又一次组织链和分配责任。

      职责链模式的主要长处在于能够减少系统的耦合度,简化对象的相互连接,同一时候增强给对象指派职责的灵活性,添加新的请求处理类也非常方便。其主要缺点在于不能保证请求一定被接收。且对于比較长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,并且在进行代码调试时不太方便。