<a target="_blank" href="http://blog.csdn.net/hguisu/article/details/7547231"> 設計模式(十二)職責鍊模式(chain of responsibility)(對象行為型)</a>
1.概述
你去政府部門求人辦事過嗎?有時候你會遇到過官員踢球推責,你的問題在我這裡能解決就解決,不能解決就推卸給另外個一個部門(對象)。至于到底誰來解決這個問題呢?政府部門就是為了可以避免屁民的請求與官員之間耦合在一起,讓多個(部門)對象都有可能接收請求,将這些(部門)對象連接配接成一條鍊,并且沿着這條鍊傳遞請求,直到有(部門)對象處理它為止。 例子1:js的事件浮升機制例子2:![]()
設計模式 ( 十二 ) 職責鍊模式(Chain of Responsibility)(對象行為)
2.問題
如果有多個對象都有可能接受請求,如何避免避免請求發送者與接收者耦合在一起呢?
3.解決方案
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.結構
一個典型的對象結構可能如下圖所示:![]()
設計模式 ( 十二 ) 職責鍊模式(Chain of Responsibility)(對象行為)
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 echo $this->_handlername, " was signed <br/>";//部門經理簽字 if( $request->getday() > 0.5 && $request->getday()<=3) { class concretehandlergeneralmanager extends handler echo $this->_handlername, " was signed <br/>";//總經理簽字 if(3 < $request->getday()){ * 請假申請 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){ public function getname(){ return $this->_name; public function setday($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(){ //簽字清單 $request = new request('guisu',3,'休息' ); $leader->setsuccessor($generalmanager); $result = $leader->handlerequest($request); * 流程3 :如果leader不在,那麼完全可以寫這樣的代碼 * manager ->generalmanager static function main3(){ $request = new request('guisu',0.1,'休息' ); $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數組就可以了,而不用修改程式。 * 個稅起征點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很龐大。那麼我們可以使用折半查找的方式: * 優化計算稅率:使用折半查找法,有效縮短時間複雜度 * 優化計算稅率:折半查找法 function optimizecomptax($income){ $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; } return $key; echo optimizecomptax($income);
10.與其他相關模式
11.總結