設計模式 十二 職責鏈模式 Chain of Responsibility (物件行為
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
設計模式(十二)職責鏈模式(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)發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得 系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任 。
4.適用性
在以下條件下使用Responsibility 鏈:
• 有多個的物件可以處理一個請求,哪個物件處理該請求執行時刻自動確定。
• 你想在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。
•可動態指定一組物件處理請求。
5.結構
一個典型的物件結構可能如下圖所示:
6. 模式的組成
抽象處理者角色(Handler:Approver):定義一個處理請求的介面,和一個後繼連線(可選)
具體處理者角色(ConcreteHandler:President):處理它所負責的請求,可以訪問後繼者,如果可以處理請求則處理,否則將該請求轉給他的後繼者。
客戶類(Client):向一個鏈上的具體處理者ConcreteHandler物件提交請求。
7. 效果
8. 純與不純的職責鏈模式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) • 系統性能將受到一定影響,而且在進行程式碼除錯時不太方便;可能會造成迴圈呼叫。
純的職責鏈模式:一個具體處理者角色處理只能對請求作出兩種行為中的一個:一個是自己處理(承擔責任),另一個是把責任推給下家。不允許出現某一個具體處理者物件在承擔了一部分責任後又將責任向下傳的情況。請求在責任鏈中必須被處理,不能出現無果而終的結局。 反之就是不純的職責鏈模式。 在一個純的職責鏈模式裡面,一個請求必須被某一個處理者物件所接收;在一個不純的職責鏈模式裡面,一個請求可以最終不被任何接收端物件所接收。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的形式。
我們吧把職責鏈模式應用到面向過程程式設計,而不是物件。例如:
個稅起徵點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
我們可以不必使用那麼多的if和elseif語句判斷。我們只要配置$taxs陣列就可以了,而不用修改程式。
如果判斷的條件很多,也就是陣列$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/>';
<?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.總結
在職責鏈模式裡,很多物件由每一個物件對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。 職責鏈模式的主要優點在於可以降低系統的耦合度,簡化物件的相互連線,同時增強給物件指派職責的靈活性,增加新的請求處理類也很方便;其主要缺點在於不能保證請求一定被接收,且對於比較長的職責鏈,請求的處理可能涉及到多個處理物件,系統性能將受到一定影響,而且在進行程式碼除錯時不太方便。