1. 程式人生 > >(二十)職責鏈模式詳解(都市異能版)

(二十)職責鏈模式詳解(都市異能版)

作者:zuoxiaolong8810(左瀟龍),轉載請註明出處,特別說明:本博文來自博主原部落格,為保證新部落格中博文的完整性,特複製到此留存,如需轉載請註明新部落格地址即可。

               凌晨兩點,魔都某出租屋。

               "God like."

               .......

               "Holy shit."

               ......

               “哈哈。老子終於超神一次啦。”伴隨著低音炮中一聲怒吼,小左在自己十來平米的出租房裡興奮的尖叫起來。近了一看,原來小左操作的J8臉30分鐘已經一身神裝,如今正在瘋狂的大殺四方。

               “今晚殺的真爽,乾脆通宵得了。”

               依依不捨的退出了超神的一局,小左摸了摸自己像漏氣了一樣的肚子,喃喃的說道:“肚子有點餓了啊,這大半夜的可怎麼辦呢。”

               

               “喂,你好,請問現在還送餐嗎?”

               “不好意思,先生,您的住址離我們太遠,請您問一下其它分店。”

               “問你妹啊,老子問了好幾家了。”心裡雖然不爽,小左嘴上還是很客氣的說道:“嗯,好的,謝謝啊。”

               .......

               “喂,你好,請問你們往XXXX小區送餐嗎?”

               “嗯,是的。先生。”

               小左心中一樂,“嗯,那好,我要一個XXX,一個XXX,一個XXX。”

               “不好意思,先生,現在太晚了,我們的送餐時間已經過了。”

               “我....去....你....妹啊。不送不早說。”心中暗罵了一句,小左直接掛了電話。

               .......

               “喂,你好,請問你們往XXXX小區送餐嗎?”

               “嗯,是的。先生。”

               “嗯,那你們送餐時間沒過吧。”有了上次的經驗,小左沒有先報選單,而是先確認對方是否過了送餐時間。

               “沒有,先生。”

               “嗯,那好,我要一個XXX,一個XXX,一個XXX。”

               “不好意思,先生,這些都已經賣完了,您看您能換別的嗎?”

               “我....換你大爺.....”

 

                半個小時過去了...

                “哎呀媽呀,累死我了。終於找到一家送餐的了。這麥當勞號稱24小時外賣是坑爹呢吧。”經過將近半個小時的折騰,小左終於找到一家比較合適的麥當勞分店,現在就只等著外賣送過來了。

                “外賣估計半個小時就到了,這一會也開不了一局,乾脆乾點別的,等吃完飯再開始。”說著,小左便開始在網上找尋設計模式的內容,這一閒下來就研究設計模式的勁頭,當真是不辱轉生前程式猿的頭銜。

                

                “啊哈,職責連模式。看起來可以解決麥當勞訂餐的問題啊,好好研究一下,給麥當勞老總提個建議,說不定賞我個幾億美金。哇哈哈。”還沒開始研究,小左已經開始YY起來。

                定義:為了避免請求的傳送者和接收者之間的耦合關係,使多個接受物件都有機會處理請求。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。

                “看這個定義,就是將一堆可以處理請求的物件連成一條鏈,然後一個一個試著處理請求。這好像是可以解決麥當勞訂餐的問題的,我先來看看我剛才苦B的訂餐過程是什麼樣子的。”

                “首先應該有一個麥當勞的分店的類,它的主要功能是可以訂餐。”

複製程式碼

package com.chain;

import java.util.Collections;
import java.util.Map;

//麥當勞分店
public class McSubbranch {
    
    private final static int MIN_DISTANCE = 500;//假設是500米以內送餐
    
    private static int count;//類計數
    
    private final int number;//分店號
    
    private int x;//分店的橫座標,用於判斷距離
    
    private int y;//分店的縱座標,用於判斷距離
    
    private Map<String, Integer> menu;//選單
    
    public McSubbranch(int x, int y, Map<String, Integer> menu) {
        super();
        this.x = x;
        this.y = y;
        this.menu = menu;
        number = ++count;
    }

    public boolean order(int x,int y,Map<String, Integer> order){
        //如果距離小於500米並且訂單中的食物不缺貨,則訂單成功,否則失敗
        if (CommonUtils.getDistance(x, y, this.x, this.y) < MIN_DISTANCE && !CommonUtils.outOfStock(menu, order)) {
            for (String name : order.keySet()) {
                menu.put(name, menu.get(name) - order.get(name));
            }
            return true;
        }else {
            return false;
        }
    }

    public Map<String, Integer> getMenu() {
        return Collections.unmodifiableMap(menu);
    }

    public String toString() {
        return "麥當勞分店第" + number + "個";
    }
    
    
    
}

複製程式碼

                “這裡面用到了一個工具類,主要是用來計算距離和判斷是否缺貨的,就是這樣寫的。”

複製程式碼

package com.chain;

import java.util.Map;
//簡單的工具類
public class CommonUtils {
    
    private CommonUtils(){}

    //計算座標之間的距離
    public static double getDistance(int x1,int y1,int x2,int y2){
        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    }
    //是否缺貨
    public static boolean outOfStock(Map<String, Integer> menu,Map<String, Integer> order){
        if (order == null || order.size() == 0) {
            return false;
        }
        if (menu == null || menu.size() == 0) {
            return true;
        }
        for (String name : order.keySet()) {
            if (menu.get(name) - order.get(name) < 0) {
                return true;
            }
        }
        return false;
    }
    
}

複製程式碼

                “下面就是我苦B的訂餐過程了,假設有五個分店的話,我訂餐的過程就是一家一家挨著去訂,直到成功為止。”

複製程式碼

package com.chain;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Client {

    public static void main(String[] args) {
        //假設初始選單都是以下這些東西
        Map<String, Integer> menu = new HashMap<String, Integer>();
        menu.put("漢堡", 5);
        menu.put("薯條", 5);
        menu.put("可樂", 5);
        menu.put("雪碧", 5);
        //假設有5個分店
        McSubbranch mcSubbranch1 = new McSubbranch(0, 0, new HashMap<String, Integer>(menu));
        McSubbranch mcSubbranch2 = new McSubbranch(100, 120, new HashMap<String, Integer>(menu));
        McSubbranch mcSubbranch3 = new McSubbranch(-100, -120, new HashMap<String, Integer>(menu));
        McSubbranch mcSubbranch4 = new McSubbranch(1000, 20, new HashMap<String, Integer>(menu));
        McSubbranch mcSubbranch5 = new McSubbranch(-500, 0, new HashMap<String, Integer>(menu));
        
        List<McSubbranch> mcSubbranchs = Arrays.asList(mcSubbranch1,mcSubbranch2,mcSubbranch3,mcSubbranch4,mcSubbranch5);
        
        //小左開始訂餐,假設小左的座標是900,20 
        Map<String, Integer> order = new HashMap<String, Integer>();
        order.put("漢堡", 2);
        order.put("可樂", 1);
        order.put("薯條", 1);
        
        print(mcSubbranchs);
        System.out.println("------------------------------------------");
        //小左開始一家一家挨著嘗試訂餐,直到成功
        for (McSubbranch mcSubbranch : mcSubbranchs) {
            if (mcSubbranch.order(900, 20, order)) {
                System.out.println("訂餐成功,接受訂單的分店是:" + mcSubbranch);
                break;
            }
        }
        System.out.println("------------------------------------------");
        print(mcSubbranchs);
    }
    
    public static void print(List<McSubbranch> mcSubbranchs){
        for (McSubbranch mcSubbranch : mcSubbranchs) {
            System.out.println("[" + mcSubbranch + "]的選單:" + mcSubbranch.getMenu());
        }
    }
}

複製程式碼

                   “這樣確實比較悲催啊,我得一家一家打電話問,太麻煩了。麥當勞這麼大一個企業,訂餐的服務竟然這麼爛哦。看我用設計模式給你優化一下吧,哈哈。”

                   “先來看看職責鏈模式的類圖,這樣比較好設計。”

                   “類圖還是比較簡單的啊,有一個通用的介面,然後就是若干個具體的處理者。按照現在麥當勞的情況來說,接口裡handleRequest方法其實就是order(訂餐)方法了,而setSuccessor方法,則是用來設定職責鏈的下一個處理者。”

                   “對於麥當勞的問題來說,每一個分店就是具體的處理者了,主要的改動應該是抽象出來一個介面以及職責鏈的連線過程,而剛才傳送訂單的時候是拆分成方法引數傳遞給訂餐方法的,現在最好是把訂單做成一個數據類。”

複製程式碼

package com.chain;

import java.util.Map;

//訂單類(相當於request,其實就是封裝一個請求)
public class Order {

    private int x;
    private int y;
    private Map<String, Integer> order;
    
    public Order(int x, int y, Map<String, Integer> order) {
        super();
        this.x = x;
        this.y = y;
        this.order = order;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public Map<String, Integer> getOrder() {
        return order;
    }

    public void setOrder(Map<String, Integer> order) {
        this.order = order;
    }
    
}

複製程式碼

                “下面便應該是分店介面了,它有兩個方法,和類圖當中的方法類似,只是名稱略有改變。”

複製程式碼

package com.chain;
//分店介面(相當於Hanlder)
public interface Subbranch {

    void setSuccessor(Subbranch subbranch);
    
    boolean handleOrder(Order order);
    
}

複製程式碼

                “下面便是麥當勞分店的實現類了,它主要的改變是添加了一個屬性(下一個分店),這應該就是鍊形成的基石了。”

複製程式碼

package com.chain;

import java.util.Collections;
import java.util.Map;

//麥當勞分店
public class McSubbranch implements Subbranch{
    
    private final static int MIN_DISTANCE = 500;//假設是500米以內送餐
    
    private static int count;//類計數
    
    private final int number;//分店號
    
    private int x;//分店的橫座標,用於判斷距離
    
    private int y;//分店的縱座標,用於判斷距離
    
    private Map<String, Integer> menu;//選單
    
    private Subbranch nextSubbranch;//下一家分店
    
    public McSubbranch(int x, int y, Map<String, Integer> menu) {
        super();
        this.x = x;
        this.y = y;
        this.menu = menu;
        number = ++count;
    }
    //設定下一家分店
    public void setSuccessor(Subbranch subbranch) {
        this.nextSubbranch = subbranch;
    }
    //按照職責鏈處理訂單
    public boolean handleOrder(Order order){
        //如果距離小於500米並且訂單中的食物不缺貨,則訂單成功,否則失敗
        if (CommonUtils.getDistance(order.getX(), order.getY(), this.x, this.y) < MIN_DISTANCE && !CommonUtils.outOfStock(menu, order.getOrder())) {
            for (String name : order.getOrder().keySet()) {
                menu.put(name, menu.get(name) - order.getOrder().get(name));
            }
            System.out.println("訂餐成功,接受訂單的分店是:" + this);
            return true;
        }
        if (nextSubbranch == null) {
            return false;
        }
        return nextSubbranch.handleOrder(order);
    }

    public Map<String, Integer> getMenu() {
        return Collections.unmodifiableMap(menu);
    }
    
    public Subbranch getNextSubbranch() {
        return nextSubbranch;
    }
    
    public String toString() {
        return "麥當勞分店第" + number + "個";
    }
    
    
}

複製程式碼

               “handleOrder方法中的邏輯就是職責鏈的精髓了,它會試圖處理請求,如果處理不了,則交給鏈中的下一個分店。剛才用的CommonUtils應該不用變了。下面就看下有了職責鏈模式之後,我的訂餐方式吧。”

複製程式碼

package com.chain;

import java.util.HashMap;
import java.util.Map;

public class Client {

    public static void main(String[] args) {
        //假設初始選單都是以下這些東西
        Map<String, Integer> menu = new HashMap<String, Integer>();
        menu.put("漢堡", 5);
        menu.put("薯條", 5);
        menu.put("可樂", 5);
        menu.put("雪碧", 5);
        //假設有5個分店
        Subbranch mcSubbranch1 = new McSubbranch(0, 0, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch2 = new McSubbranch(100, 120, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch3 = new McSubbranch(-100, -120, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch4 = new McSubbranch(1000, 20, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch5 = new McSubbranch(-500, 0, new HashMap<String, Integer>(menu));
        
        //以下設定職責鏈
        mcSubbranch4.setSuccessor(mcSubbranch5);
        mcSubbranch3.setSuccessor(mcSubbranch4);
        mcSubbranch2.setSuccessor(mcSubbranch3);
        mcSubbranch1.setSuccessor(mcSubbranch2);
        //小左開始訂餐,假設小左的座標是900,20 
        Map<String, Integer> order = new HashMap<String, Integer>();
        order.put("漢堡", 2);
        order.put("可樂", 1);
        order.put("薯條", 1);
        
        print(mcSubbranch1);
        System.out.println("------------------------------------------");
        
        //小左開始訂餐,直接找mcSubbranch1的這一家分店訂餐即可
        mcSubbranch1.handleOrder(new Order(900, 20, order));
        
        System.out.println("------------------------------------------");
        print(mcSubbranch1);
    }
    
    public static void print(Subbranch subbranch){
        if (subbranch == null ) {
            return;
        }
        do {
            if (subbranch instanceof McSubbranch) {
                System.out.println("[" + subbranch + "]的選單:" + ((McSubbranch) subbranch).getMenu());
            }
        } while ((subbranch = ((McSubbranch) subbranch).getNextSubbranch()) != null);
    }
    
}

複製程式碼

                “輸出結果和剛才是一樣的,不過這下我訂餐就好辦多了,直接找第一家分店訂餐就行,至於到最後誰給我送餐,我就不用管了。”

                “職責鏈模式果然強大啊,哈哈。不過現在還是有一點缺陷,那就是訂餐的時候我還得記著應該找哪家分店,如果麥當勞有一個專門的訂餐管理部門就好了,這樣的話,就更容易找到該找誰訂餐了。下面我就再新增一個訂餐管理部門,完善一下吧。”

複製程式碼

package com.chain;
//訂餐管理部門,它並不是職責鏈模式中的角色
//但是在職責鏈模式中,通常情況下會有一個類專門負責維護職責鏈
//在本例中,這個類稱為訂餐管理部門更合適
public class OrderManager {
    
    private static OrderManager orderManager = new OrderManager();

    private Subbranch head;
    
    private Subbranch last;
    
    private OrderManager(){}
    
    public static OrderManager getOrderManager(){
        return orderManager;
    }

    //註冊分店
    public void registerSubbranch(Subbranch... subbranchs){
        for (Subbranch subbranch : subbranchs) {
            registerSubbranch(subbranch);
        }
    }
    public void registerSubbranch(Subbranch subbranch){
        if (head == null) {
            last = head = subbranch;
        }else {
            last.setSuccessor(subbranch);
            last = subbranch;
        }
    }
    
    public boolean handleOrder(Order order){
        return head.handleOrder(order);
    }
    
}

複製程式碼

                “其餘的都不用變,這下我就使用訂餐管理部門(可以理解為麥當勞總店或者總服務檯都可以)訂餐試一下吧。”

複製程式碼

package com.chain;

import java.util.HashMap;
import java.util.Map;

public class Client {

    public static void main(String[] args) {
        //假設初始選單都是以下這些東西
        Map<String, Integer> menu = new HashMap<String, Integer>();
        menu.put("漢堡", 5);
        menu.put("薯條", 5);
        menu.put("可樂", 5);
        menu.put("雪碧", 5);
        //假設有5個分店
        Subbranch mcSubbranch1 = new McSubbranch(0, 0, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch2 = new McSubbranch(100, 120, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch3 = new McSubbranch(-100, -120, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch4 = new McSubbranch(1000, 20, new HashMap<String, Integer>(menu));
        Subbranch mcSubbranch5 = new McSubbranch(-500, 0, new HashMap<String, Integer>(menu));
        
        //註冊5個分店
        OrderManager.getOrderManager().registerSubbranch(mcSubbranch1,mcSubbranch2,mcSubbranch3,mcSubbranch4,mcSubbranch5);
        
        //小左開始訂餐,假設小左的座標是900,20 
        Map<String, Integer> order = new HashMap<String, Integer>();
        order.put("漢堡", 2);
        order.put("可樂", 1);
        order.put("薯條", 1);
        
        print(mcSubbranch1);
        System.out.println("------------------------------------------");
        
        //小左開始訂餐,直接找訂餐管理部門訂餐
        OrderManager.getOrderManager().handleOrder(new Order(900, 20, order));
        
        System.out.println("------------------------------------------");
        print(mcSubbranch1);
    }
    
    public static void print(Subbranch subbranch){
        if (subbranch == null ) {
            return;
        }
        do {
            if (subbranch instanceof McSubbranch) {
                System.out.println("[" + subbranch + "]的選單:" + ((McSubbranch) subbranch).getMenu());
            }
        } while ((subbranch = ((McSubbranch) subbranch).getNextSubbranch()) != null);
    }
    
}

複製程式碼

                “現在有了分店,直接向管理部門註冊一下就好了,到時候有訂單就會自動由管理部門一次分發下去了。這一次輸出的結果和第一次應該也是一樣的。這一下訂餐就更方便了,我只管記著麥當勞訂餐部門的電話就行,至於它到底有幾個分店,我就完全不用管啦。”

                “下面我就總結一下職責鏈的好處吧,到時候給麥當勞老總打個電話描述一下,哈哈。”

                用了職責鏈模式之後,主要的好處是下面兩點。

                1、不再需要記憶所有分店的號碼和聯絡方式,然後一個一個去訂餐。

                2、不需要知道麥當勞的內部管理結構,正因為這樣,麥當勞再開多少分店,訂餐的客戶都不需要關心,而按照以前的方式,麥當勞每多開一個分店,客戶都有可能需要多記憶一個分店的聯絡方式。

                用專業點的語言來說,就是下面兩點。

                1、客戶端與具體的處理者解耦,客戶端只認識一個Hanlder介面,降低了客戶端(即請求傳送者)與處理者的耦合度。

                2、客戶端和處理者都不關心職責鏈的具體結構,而是交給職責鏈的創造者(在上述例子當中則是交給了OrderManager),也正因為如此,當在職責鏈中新增處理者的時候,這對客戶端和處理者來說,都是透明的,二者不知道也不必要知道職責鏈的變化。

                “職責鏈模式的好處應該就是這些啦,下面我來看看剛才的類圖吧。”

                “和標準的職責鏈模式類圖幾乎一模一樣,少了一個具體的處理者,不過這並不影響類圖所要傳達的意義。不過這個類圖也有點太簡單了,不過也正因為簡單,從類圖裡一眼就能看出來,職責鏈模式的精髓就在於那一條神奇的聚合線啊。”

 

                “叮咚...”

                “咦?我訂的餐到啦,哈哈。”

 

 

 

版權宣告

 


作者:zuoxiaolong(左瀟龍)

出處:部落格園左瀟龍的技術部落格--http://www.cnblogs.com/zuoxiaolong

您的支援是對博主最大的鼓勵,感謝您的認真閱讀。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。