設計模式 ( 十二 ) 職責鏈模式(Chain of Responsibility)(對象行為

1.概述

       你去政府部門求人辦事過嗎?有時候你會遇到過官員踢球推責,你的問題在我這里能解決就解決,不能解決就推卸給另外個一個部門(對象)。至于到底誰來解決這個問題呢?政府部門就是為了可以避免屁民的請求與官員之間耦合在一起,讓多個(部門)對象都有可能接收請求,將這些(部門)對象連接成一條鏈,并且沿著這條鏈傳遞請求,直到有(部門)對象處理它為止。

例子1:js的事件浮升機制

1.jpg

例子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)發出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。

4.適用性

在以下條件下使用Responsibility 鏈:

? 有多個的對象可以處理一個請求,哪個對象處理該請求運行時刻自動確定。

? 你想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。

?可動態指定一組對象處理請求。

5.結構

        1.jpg

一個典型的對象結構可能如下圖所示:

2.jpg

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  
  
/** 
 * 加入在公司里,如果你的請假時間小于0.5天,那么只需要向leader打聲招呼就OK了。 
  如果0.5<請假天數<=3天,需要先leader打聲招呼,要不然leader不知你跑哪里,然后部門經理直接簽字。 
  如果3<請假天數 天,需要先leader打聲招呼,然后到部門經理簽字,最好總經經理確認簽字, 
  這樣就是個list。也是個不純的職責鏈,因為每個對象可能處理一部分后,就需要傳給下個對象來處理。 
   
 */  
  
/** 
* 純職責鏈模式  
*  
* 為解除請求的發送者和接收者之間的耦合,而使用多個對象都用機會處理這個請求,將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它  
* @author guisu 
*  
*/   
/** 
 * 抽象處理者角色(Handler:Approver):定義一個處理請求的接口,和一個后繼連接(可選) 
 * 
 */  
abstract class Handler  
{  
    protected $_handler = null;  
    protected $_handlerName = null;  
      
    public function setSuccessor($handler)  
    {  
        $this->_handler = $handler;  
    }  
      
    protected  function _success($request)  
    {  
        echo $request->getName(), '\' request was passed  <br/>';  
        return true;  
    }  
    abstract function handleRequest($request);  
}  
/** 
 * 具體處理者角色(ConcreteHandler:President):處理它所負責的請求,可以訪問后繼者,如果可以處理請求則處理,否則將該請求轉給他的后繼者。 
 * 
 */  
class ConcreteHandlerLeader extends Handler  
{  
    function __construct($handlerName){  
        $this->_handlerName = $handlerName;  
    }  
    public function handleRequest($request)  
    {  
        echo $this->_handlerName, ' was known <br/>';//已經跟leader招呼了  
        if($request->getDay() < 0.5) {  
            return $this->_success($request);  
        }   
        if ($this->_handler instanceof Handler) {  
            return $this->_handler->handleRequest($request);  
        }  
    }  
}  
/** 
 * Manager 
 * 
 */  
class ConcreteHandlerManager extends Handler  
{  
    function __construct($handlerName){  
        $this->_handlerName = $handlerName;  
    }  
      
    public function handleRequest($request)  
    {  
        echo $this->_handlerName, " was signed <br/>";//部門經理簽字  
        if( $request->getDay() > 0.5 && $request->getDay()<=3) {  
            return $this->_success($request);  
        }   
        if ($this->_handler instanceof Handler) {  
            return $this->_handler->handleRequest($request);  
        }  
    }  
}  
class ConcreteHandlerGeneralManager extends Handler  
{  
    function __construct($handlerName){  
        $this->_handlerName = $handlerName;  
    }  
      
    public function handleRequest($request)  
    {  
        echo $this->_handlerName, " was signed <br/>";//總經理簽字  
        if(3 < $request->getDay()){  
            return $this->_success($request);  
        }  
        if ($this->_handler instanceof Handler) {  
            return $this->_handler->handleRequest($request);  
        }  
    }  
}  
/** 
 * 請假申請 
 * 
 */  
class   Request  
{  
    private $_name;  
    private $_day;  
    private $_reason;  
  
    function __construct($name= '', $day= 0, $reason = ''){  
        $this->_name = $name;  
        $this->_day = $day;  
        $this->_reason = $reason;  
    }  
      
    public function setName($name){  
        $this->_name = $name;  
    }  
    public function getName(){  
        return  $this->_name;  
    }  
      
    public function setDay($day){  
        $this->_day = $day;  
    }  
    public function getDay(){  
        return  $this->_day ;  
    }  
      
    public function setReason($reason ){  
         $this->_reason = $reason;  
    }  
    public function getReason( ){  
        return  $this->_reason;  
    }  
}  
  
  
class client{  
      
    /** 
     *流程1:leader-> manager ->generalManager 
     * 
     */  
    static function main(){  
          
        $leader = new ConcreteHandlerLeader('$leader');  
        $manager = new ConcreteHandlerManager('$manager');  
        $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
          
        //請求實例  
        $request = new Request('guisu',4,'休息' );  
          
        $leader->setSuccessor($manager);  
        $manager->setSuccessor($generalManager);  
        $result =  $leader->handleRequest($request);  
    }  
      
    /** 
     * 流程2 : 
     * leader ->generalManager 
     */  
    static function main2(){  
        //簽字列表  
        $leader = new ConcreteHandlerLeader('$leader');  
        $manager = new ConcreteHandlerManager('$manager');  
        $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
          
        //請求實例  
        $request = new Request('guisu',3,'休息' );  
        $leader->setSuccessor($generalManager);  
        $result = $leader->handleRequest($request);  
    }  
      
    /** 
     * 流程3 :如果leader不在,那么完全可以寫這樣的代碼 
     * manager ->generalManager 
     */  
    static function main3(){  
        //簽字列表  
        $leader = new ConcreteHandlerLeader('$leader');  
        $manager = new ConcreteHandlerManager('$manager');  
        $generalManager = new ConcreteHandlerGeneralManager('$generalManager');  
          
        //請求實例  
        $request = new Request('guisu',0.1,'休息' );  
        $leader->setSuccessor($manager);  
        $manager->setSuccessor($generalManager);  
        $result = $manager->handleRequest($request);  
    }  
}  
  
  
  
client::main3();


對于怎么維護職責的鏈子,《設計模式》僅僅說自己去實現,可以使用list或者map的形式。

我們吧把職責鏈模式應用到面向過程編程,而不是對象。例如:

  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  
  
/** 
 * 個稅起征點3500元 
級數   全月應納稅所得額            稅率(%) 
   1    不超過1500元的                3 
   2    超過1500元至4500元的部分      10 
   3    超過4500元至9000元的部分      20 
   4    超過9000元至35000元的部分     25 
   5    超過35000元至55000元的部分    30 
   6    超過55000元至80000元的部分    35 
   7    超過80000元的部分             45 
*/  
/** 
 * 這個例子還沒有扣除社保公積金等 
 */  
//收入  
$income = 84000;  
//稅率  
$taxs[1] = array(1500, 0.03);  
$taxs[2] = array(4500, 0.1);  
$taxs[3] = array(9000, 0.2);  
$taxs[4] = array(35000, 0.25);  
$taxs[5] = array(55000, 0.30);  
$taxs[6] = array(80000, 0.35);  
$taxs[7] = array(1000000000, 0.45);  
  
/** 
 * 計算稅率 
 * 
 * @param int $income 
 * @return int 
 */  
function compTax($income){  
    global $taxs;  
    //個稅起點  
    $taxStart  = 3500;  
    $incomeTax = $income > $taxStart ?($income - $taxStart) : 0;  
    $flag = false;  
    foreach ($taxs as $values) {  
        if ($incomeTax < $values[0]  ) {  
            $compTax = $incomeTax * $values[1];  
            break;  
        }else{  
            continue;  
        }  
    }  
    return $compTax;  
}  
  
echo compTax($income);  
echo '-------------------<br/>';

如果判斷的條件很多,也就是數組$taxs很龐大。那么我們可以使用折半查找的方式:

<?php  
  
/** 
 * 個稅起征點3500元 
級數   全月應納稅所得額            稅率(%) 
   1    不超過1500元的                3 
   2    超過1500元至4500元的部分      10 
   3    超過4500元至9000元的部分      20 
   4    超過9000元至35000元的部分     25 
   5    超過35000元至55000元的部分    30 
   6    超過55000元至80000元的部分    35 
   7    超過80000元的部分             45 
*/  
/** 
 * 這個例子還沒有扣除社保公積金等 
 */  
//收入  
$income = 84000;  
//稅率  
$taxs[1] = array(1500, 0.03);  
$taxs[2] = array(4500, 0.1);  
$taxs[3] = array(9000, 0.2);  
$taxs[4] = array(35000, 0.25);  
$taxs[5] = array(55000, 0.30);  
$taxs[6] = array(80000, 0.35);  
$taxs[7] = array(1000000000, 0.45);  
  
/** 
 * 優化計算稅率:使用折半查找法,有效縮短時間復雜度 
 */  
  
/** 
 * 優化計算稅率:折半查找法 
 * 
 * @param int $income 
 * @return int 
 */  
function optimizeCompTax($income){  
    //個稅起點  
    global $taxs;  
    $taxStart  = 3500;  
    $incomeTax = $income > $taxStart ?($income - $taxStart) : 0;  
    $key = bSearch($taxs, $incomeTax, 1);  
    return $incomeTax * $taxs[$key][1];  
}  
  
/** 
 *  
 * 折半查找法 
 * @param unknown_type $taxs 
 * @param unknown_type $incomeTax 
 * @return unknown 
 */  
function bSearch($taxs, $incomeTax, $start = 0){  
      
    $incomeTax = intval($incomeTax);  
    ksort($taxs);  
    foreach ($taxs as $key => $values) {  
        $low = $key;  
        break;  
    }  
    if ($incomeTax <=0 ) {  
        return $low;  
    }  
    $high = count($taxs) + $low -1;  
    while  ( $low < $high){  
        $mid = intval(($low + $high)/2) ;  
        if ( $incomeTax < $taxs[$mid][0] ) {//后半區找  
            $high = $mid;  
        } else { //前半區找  
            $low = $mid ;  
        }  
        /** 
         * 由于這個不是完全折半查找 
         * 只有兩個元素的時候,需要判斷 
         */  
        if (($high - $low) ==1) {  
            if ( $incomeTax > $taxs[$low][0] ) {  
                $key = $high;  
            } else{  
                $key = $low;  
            }  
            break;  
        }  
    }  
    return $key;  
}  
echo optimizeCompTax($income);

10.與其他相關模式

職責鏈常與Composite組合模式一起使用。這種情況下,一個構件的父構件可作為它的后繼

11.總結      

職責鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。

      職責鏈模式的主要優點在于可以降低系統的耦合度,簡化對象的相互連接,同時增強給對象指派職責的靈活性,增加新的請求處理類也很方便;其主要缺點在于不能保證請求一定被接收,且對于比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到一定影響,而且在進行代碼調試時不太方便。

轉自:http://blog.csdn.net/hguisu/article/details/7547231

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2920

(0)
s19930811s19930811
上一篇 2015-07-15
下一篇 2015-07-15

相關推薦

  • 學習宣言

     學習計劃:參考N24學習時間與線路圖,盡最大努力完成。 目標:學習好Linux,提升自我價值,找一份待遇好的工作。 宣言:每一次輕易的放棄,都是人生的一處敗筆,為了夢想,加油!

    Linux資訊 2016-10-26
  • MarkdownPad2 簡單使用指南

    MarkdownPad2 簡單使用指南 一級標題 二級標題 三級標題加代碼 四級標題 這里是加粗 這里是正文and English 888 這里有正文嵌入代碼這種樣式 這里是代碼塊 這種使用的代碼塊 還有引用 這種格式 這種格式再加粗 拖下來一點的格式加個代碼 斜體 斜體內的加粗 自我改好的樣式 注意是這種風格的樣式! 附:一個很全的顏色代碼網頁h…

    2016-11-13
  • 第二周作業

    Linux上的文件管理類命令都有哪些,其常用的使用方法及其相關實例演示 理論上,linux一切皆文件,對于linux的管理命令均算是文件管理命令。 Linux文件類型常見的有:常規文件、目錄文件、塊設備文件、字符設備文件、符號鏈接文件、命名管道、套接字文件等。 -:常規文件 [root@iZ25c28fe7fZ ~]# ls -lh checkmount.s…

    Linux干貨 2017-09-25
  • 第六周作業

    請詳細總結vim編輯器的使用并完成以下練習題 1、復制/etc/rc.d/rc.sysinit文件至/tmp目錄,將/tmp/rc.sysinit文件中的以至少一個空白字符開頭的行的行首加#; %s@^[[:space:]]\+@#&@g 2、復制/boot/grub/grub.conf至/tmp目錄中,刪除/tmp/grub.conf文件中的行首的…

    Linux干貨 2017-03-03
  • 處理交換文件和分區

    處理交換文件和分區 交換分區是系統RAM的補充 ?  基本設置包括:  1 創建交換分區或者文件  2 使用mkswap寫入特殊簽名  3 在/etc/fstab文件中添加適當的條目  4 使用swapon -a 激活交換空間 掛載交換分區 ? 啟用:swapon swapon [OPTION]……

    Linux干貨 2016-09-01
欧美性久久久久