(十二)命令模式詳解(故事版)


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

                 背景:小左是魔都某公司技術部的一名屌絲程序猿,每天的工作就是維護一個20世紀的古董級項目,由於公司不大,所以公司很多制度不太完善,導致小左每天都郁悶異常,只是靠偶爾的在剛畢業的小小美女程序媛旁邊露一手,來豐富自己的精神生活。

                 某一天下午一點半,陰。

                 小左正趴在桌子上,迷迷糊糊想着某些個不切實際的美夢,突然QQ上“滴滴滴”的聲音,將本就只是半夢半醒的小左給吵醒了。無奈之下,小左只好懶懶散散的抬起頭,打開了QQ消息。

                 業務人員A:“小左啊,我們的XX列表需要加一個顯示項和一個搜索項,你看你啥時候能做完啊,這個很急。”

                 小左眯縫着眼一看,心里小小一盤算,不算太復雜,於是就匆匆的回復了。

                 小左:“這個啊,很快,下午就搞定。”

                 於是小左摩拳擦掌的就開始准備下手,結果eclipse還沒打開,QQ就又響起來了。

                 業務人員B:“小左啊,我們的XX頁面需要改一下啊,得多添幾個保存的項,你看你啥時候能做完,給我個時間?”

                 心里嘀嘀咕咕的盤算一下,小左就回復到:“大概需要大半天吧,明天搞定成不?”

                 業務人員B:“不行啊,這個很急啊,最好今天就做完,你看你加加班,搞定它,沒問題吧?”

                 小左心里一邊罵娘,一邊回復到:“好吧,我盡快。”

                 關了QQ窗口,小左心里開始犯嘀咕:“這下活多了,得趕緊做,爭取一下,一下午做兩個功能差不多,幸虧自己在給完成時間時多加了一點。”


                 剛要下手,小左的QQ又響了起來。

                 業務人員C:“小左啊,我剛帶一個客戶來做業務,我不小心把東西填錯了,你看你能不能給我改一下,客戶在這等着呢,着急啊。”

                 小左心里已經開始抓狂了,但還是淡定的回復到:“好吧,我看看。”

                 不過剛連上生產環境的數據庫,select語句還沒寫完,小左的QQ又響起來了。

                 業務人員D:“小左啊,我們的系統為什么XX列表點搜索不好用啊,你趕緊把這個問題解決啊,這么簡單的系統怎么這么多問題啊。”

                 小左此時已經徹底被激怒,一個程序猿最忍受不了的就是被不懂程序的人指指點點,於是沒好氣的回復到:“盡量吧,這會太忙,沒空。”


                 這一下,估計是剛畢業的小美女聽到了不斷的QQ消息聲,竟然主動和小左搭話。

                 小甜甜:“噢爸(韓語),你好像很忙啊,要不要小妹幫幫你啊。”

                 小左:“沒事,你剛來公司沒多久,業務還不太熟悉,這點事我分分鍾就搞定。”

                 說完,小左還揚起嘴角,擺了個超自信的表情,簡直是猥瑣到極點。

                 不過這一招對付剛畢業的單純小姑娘還真好使,只見小甜甜一臉的崇拜相,笑着回道:“你好棒哦,謝謝噢爸的照顧啊。”


                 這不吹牛不要緊,苦逼的小左愣是獨自加班到夜里十一點半,把今天接到的任務都給搞定了,為的就是在小甜甜面前證明自己的能力。這血淋淋的事實說明了,屌絲的生活中,隨便一個有點姿色的美女都足以秒殺屌絲,並且讓其賣命。

                 托着疲憊的身體回到家中,小左到了家里就往床上一躺,衣服襪子扔的滿屋子都是,也懶得洗臉刷牙,就准備睡覺了。

                 忽然小左猛地坐起來,自言自語到:“對啊,今晚該寫博客了,好久沒寫了,最近都忙的不行了。”

                 想到這些,小左覺得剛才的困意一瞬間就消失無蹤,這也算是屌絲的一大氣質了吧---夜里歡。

                 隨着電腦緩慢的啟動,小左的腦子中就開始飄盪着今天的事,“由於公司的制度不完善,人員缺乏,導致業務人員一有什么事情,全部都來找自己。本來自己就已經身兼數職,現在還要每天面臨業務員們的各種炮轟,實在是有點受不了啊。”

                 於是,小左想着想着,就打算將這個問題寫出來,看能不能從代碼里找到現實中解決問題的答案。

                 在鍵盤前猶豫片刻后,小左先把自己用JAVA代碼解釋了一遍。

package com.command;

public class Programmer {

    private String name;

    public Programmer(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    public void handleDemand(){
        System.out.println( name + "處理新需求");
    }
    
    public void handleBug(){
        System.out.println( name + "處理bug");
    }
    
    public void handleProblem(){
        System.out.println( name + "處理線上問題");
    }
    
}

                看着自己的功能,小左不禁感嘆,“自己就是一個機器啊”。感嘆之余,小左又繼續摸着下巴思考了片刻,寫出了業務人員的類。

package com.command;

public class Salesman {

    private String name;

    public Salesman(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    public void putDemand(Programmer programmer){
        System.out.println( "業務員" + name + "提出新需求");
        programmer.handleDemand();
    }
    
    public void putBug(Programmer programmer){
        System.out.println( "業務員" + name + "提出bug");
        programmer.handleBug();
    }
    
    public void putProblem(Programmer programmer){
        System.out.println( "業務員" + name + "提出線上問題");
        programmer.handleProblem();
    }
}

                最后,小左就干脆拿今天的工作為例,寫了一下一天的工作。

package com.command;

public class Work{

    public static void main(String[] args) {
        Programmer xiaozuo = new Programmer("小左");
        
        Salesman salesmanA = new Salesman("A");
        salesmanA.putDemand(xiaozuo);
        
        Salesman salesmanB = new Salesman("B");
        salesmanB.putDemand(xiaozuo);
        
        Salesman salesmanC = new Salesman("C");
        salesmanC.putBug(xiaozuo);
        
        Salesman salesmanD = new Salesman("D");
        salesmanD.putProblem(xiaozuo);
    }
    
}


                看着運行的結果,小左不禁發出一聲冷笑,暗自為自己感到悲哀。仔細的看着代碼,小左開始一一分析自己的悲哀。

                1,業務員和自己的耦合度太高,導致每個業務人員都可以直接命令自己,這導致自己心里很不爽。

                2,由於自己本身只是個程序猿,所以自己對公司的業務並不是特別擅長,很難給任務制定優先級,給任務排序,自己所擅長的還是碼代碼,結果造成的后果就是經常按時完成了一個不重要的任務,但不小心卻將很重要的任務向后推遲了,如果業務人員告狀,自己可能就要挨批。可是這么多業務人員提問題,到底誰輕誰重,我哪知道。

                3,如果任務堆積過多的時候,自己有時會忘記其中的一個甚至幾個,人畢竟精力有限,如果任務多了,難免會出現這種情況,結果有的業務人員就直接告狀到老板那里,實在是憋屈。

                4,由於任務是隨即產生的,業務人員什么時候想到任務就隨時給自己,所以沒有一個整體的規划,很容易導致自己加班。        

                “看來,說來說去,還是業務人員跟我太緊密了啊。”小左嘆氣道。

                由於小左最近在研究設計模式,所以就想到了是不是可以用設計模式來解決自己和業務人員之間的問題。於是小左開始在度娘上面找尋適合的設計模式。

                “有了,命令模式應該是可以解決的,看它的定義好像挺符合的”。電腦前的小左突然一拍大腿,大叫道。

                定義:在軟件系統中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為對象,實現二者之間的松耦合。這就是命令模式(Command Pattern)。

                看着這個定義,小左心中想道:“命令模式中所說的兩個角色,不正是我和業務人員嗎。業務人員是行為請求者,他們請求我,噢,不,應該說命令我產生編碼,修改bug和處理線上問題的行為,而我就去實現或者說執行這些行為。況且,看命令模式的定義,還可以支持記錄,我正需要這個記錄啊,否則每次任務太多,忘了哪個任務,都要我挨批。”

               於是小左迫不及待的開始研究命令模式的類圖,試圖從中找到解決的辦法。

 

               “不對啊,這個模式當中好像有一個invoker(調用者),這會是什么樣的人呢?”小左看着命令模式的類圖,不禁有些疑惑。

               小左看到類圖的時候,一下就看出自己便是Receiver,而業務人員便是Client,可是這個Invoker是什么呢?從類圖上看,小左知道,這應該是一個特別的人,它是專門用來記錄業務人員提出的需求,bug以及線上問題等等,並且還要負責通知我完成各個任務。

               “我擦,這不就是產品經理(ProductManager)嗎。”小左腦子中忽然閃出這個名詞。

               “這下有了,產品經理負責接收業務人員的各個任務,然后過濾和排好優先級以后再交代給我完成,並且他可以記住所有接受過的任務,這樣我也不會再忘記了,因為有他提醒着我呢。”

               說干就干,小左立馬就開始嘗試用命令模式去處理自己和業務人員的緊耦合問題,而解決這個問題最直接的手段就是添加了一個產品經理,並且將各個任務都抽象成一個對象,這樣產品經理就可以管理這些對象了,或者也可以說是管理這些任務。

               小左覺得自己的類是不需要變化的,主要變化的是要添加一組抽象的行為對象,還有改變客戶端調用的方式,也就是業務人員的類。

               那么首先是添加命令接口和具體的命令,不過這里似乎叫任務(Task)更合適,下面就寫。

package com.command;

public interface Task {

    void handle();
    
}

              快速的寫出這個接口,小左腦子里開始思考具體的命令應該是哪幾個,而且具體的命令是要關聯一個接受者的,而接受者正是自己,也就是Programmer。

              “有了,應該有這幾個具體的命令。”有了思路,小左就開始瘋狂的敲打鍵盤碼代碼,寫出了下面幾個具體的任務。

package com.command;

public class Demand implements Task{

    private Programmer programmer;
    
    public Demand(Programmer programmer) {
        super();
        this.programmer = programmer;
    }
    
    public void handle() {
        programmer.handleDemand();
    }

    public String toString() {
        return "Demand [programmer=" + programmer.getName() + "]";
    }
    
}
package com.command;

public class Bug implements Task{

    private Programmer programmer;
    
    public Bug(Programmer programmer) {
        super();
        this.programmer = programmer;
    }
    
    public void handle() {
        programmer.handleBug();
    }

    public String toString() {
        return "Bug [programmer=" + programmer.getName() + "]";
    }
    
}
package com.command;

public class Problem implements Task{

    private Programmer programmer;
    
    public Problem(Programmer programmer) {
        super();
        this.programmer = programmer;
    }
    
    public void handle() {
        programmer.handleProblem();
    }

    public String toString() {
        return "Problem [programmer=" + programmer.getName() + "]";
    }
    
}

              “這下妥了,這些任務包括了新的需求,bug和線上問題等等這些種類,而接受者正是我自己,或者說是程序猿,所以handle方法里,應該使用程序猿處理這個任務。”頓了一下,小左繼續自言自語道:“這下就差一個最主要的角色,產品經理了,我來試着寫一下。”

package com.command;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ProductManager {
    
    private static final int TASK_NUMBER_IN_DAY = 4;//一天最多分派掉四個任務,多了推到第二天

    private List<Task> taskList;
    
    public ProductManager() {
        super();
        taskList = new ArrayList<Task>();
    }
    
    //接受一個任務
    public void receive(Task task){
        taskList.add(task);
    }
    //分配給程序猿任務,督促程序猿完成
    public void assign(){
        Task[] copy = new Task[taskList.size() > TASK_NUMBER_IN_DAY ? taskList.size() - TASK_NUMBER_IN_DAY : 0];
        for (int i = 0; i < TASK_NUMBER_IN_DAY && i < taskList.size(); i++) {
            taskList.get(i).handle();
        }
        System.arraycopy(taskList.toArray(), TASK_NUMBER_IN_DAY > taskList.size() ? taskList.size() : TASK_NUMBER_IN_DAY, copy, 0, copy.length);
        taskList = Arrays.asList(copy);
    }
    //打印剩下的任務
    public void printTaskList(){
        if (taskList == null || taskList.size() == 0) {
            System.out.println("----------當前無任務--------");
            return;
        }
        System.out.println("---------當前剩下的任務列表--------");
        for (Task task : taskList) {
            System.out.println(task);
        }
        System.out.println("----------------------------------");
    }
}

                “仔細想想,產品經理負責的任務還挺多呢,比如優先級排序,任務過濾,需求轉化等等,怪不得產品經理要比程序猿工資高呢。不過貌似全部寫出來有點復雜啊,就讓他負責記錄下任務,然后分配下任務吧。”小左皺着眉頭自言自語道。

                “不過這樣的話,業務員也應該變一下了,他是要認識產品經理的。”

package com.command;

public class Salesman {

    private String name;

    public Salesman(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    public void putDemand(ProductManager productManager,Programmer programmer){
        System.out.println( "業務員" + name + "提出新需求");
        productManager.receive(new Demand(programmer));
    }
    
    public void putBug(ProductManager productManager,Programmer programmer){
        System.out.println( "業務員" + name + "提出bug");
        productManager.receive(new Bug(programmer));
    }
    
    public void putProblem(ProductManager productManager,Programmer programmer){
        System.out.println( "業務員" + name + "提出線上問題");
        productManager.receive(new Problem(programmer));
    }
}

                “這下好了,業務人員再也不能讓我直接去寫代碼了,必須得發給產品經理,再讓產品經理分配給我。這下我看下我的工作應該是什么樣子了。”

package com.command;

public class Work {

    public static void main(String[] args) {
        Programmer xiaozuo = new Programmer("小左");
        ProductManager productManager = new ProductManager();
        
        Salesman salesmanA = new Salesman("A");
        Salesman salesmanB = new Salesman("B");
        Salesman salesmanC = new Salesman("C");
        Salesman salesmanD = new Salesman("D");
        
        salesmanA.putDemand(productManager, xiaozuo);
        salesmanB.putDemand(productManager, xiaozuo);
        salesmanB.putBug(productManager, xiaozuo);
        salesmanC.putDemand(productManager, xiaozuo);
        salesmanC.putProblem(productManager, xiaozuo);
        salesmanD.putDemand(productManager, xiaozuo);
        
        System.out.println("第一天產品經理分配任務");
        productManager.assign();
        productManager.printTaskList();
        System.out.println("第二天產品經理分配任務");
        productManager.assign();
        productManager.printTaskList();
    }
    
}


                  小左看着運行結果和代碼開始思考,“這樣和標准的命令模式幾乎已經一樣了,只是設計模式還是不能死搬硬套啊,有些地方還是有些別扭。比如業務人員不應該再依賴於程序猿,業務人員應該只依賴產品經理就可以了,他不應該負責把任務對應給我,而應該是產品經理決定某一個任務由誰去處理的。我還得把這個地方再改一下,要不按照現在的樣子,業務人員還是能把任務分配到我身上。不過目前的好處是,我不需要當時就做了,而是等着產品經理分配。從結果就看出來了,剛才是業務人員發個任務我就做一個,現在是產品經理分配了一天的任務,然后一起做。”

                  “按照剛才想的思路,產品經理應該要認識所有的程序猿,而且還要提供一個可以選擇程序猿的方法。”小左拖着下巴喃喃的說道,說完以后就開始修改剛才的代碼。

package com.command;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ProductManager {
    
    private static final int TASK_NUMBER_IN_DAY = 4;//一天最多分派掉四個任務,多了推到第二天

    private List<Task> taskList;
    
    private List<Programmer> programmerList;//產品經理應該認識所有的程序猿
    
    private int currentIndex;
    
    public ProductManager(Programmer... programmers) {
        super();
        if (programmers == null || programmers.length == 0) {
            throw new RuntimeException("產品經理手下沒有程序員,任務分配不出去,無法正常工作!");
        }
        taskList = new ArrayList<Task>();
        programmerList = Arrays.asList(programmers);
    }
    
    //接受一個任務
    public void receive(Task task){
        taskList.add(task);
    }
    
    public void assign(){
        Task[] copy = new Task[taskList.size() > TASK_NUMBER_IN_DAY ? taskList.size() - TASK_NUMBER_IN_DAY : 0];
        for (int i = 0; i < TASK_NUMBER_IN_DAY && i < taskList.size(); i++) {
            taskList.get(i).handle();
        }
        System.arraycopy(taskList.toArray(), TASK_NUMBER_IN_DAY > taskList.size() ? taskList.size() : TASK_NUMBER_IN_DAY, copy, 0, copy.length);
        taskList = Arrays.asList(copy);
    }
    //產品經理可以選擇程序猿,簡單的循環選取。
    public Programmer chooseProgrammer(){
        return programmerList.get(currentIndex == programmerList.size() ? 0 : currentIndex++);
    }
    
    public void printTaskList(){
        if (taskList == null || taskList.size() == 0) {
            System.out.println("----------當前無任務--------");
            return;
        }
        System.out.println("---------當前剩下的任務列表--------");
        for (Task task : taskList) {
            System.out.println(task);
        }
        System.out.println("----------------------------------");
    }
}

                “這下業務人員就不需要認識我了,哈哈。”小左邊YY着有了產品經理的美好生活,一邊將業務人員的類改成了下面的樣子。

package com.command;

public class Salesman {

    private String name;
    
    private ProductManager productManager;

    public Salesman(String name) {
        super();
        this.name = name;
    }
    
    public Salesman(String name, ProductManager productManager) {
        super();
        this.name = name;
        this.productManager = productManager;
    }

    public void putDemand(){
        System.out.println( "業務員" + name + "提出新需求");
        productManager.receive(new Demand(productManager.chooseProgrammer()));
    }
    
    public void putBug(){
        System.out.println( "業務員" + name + "提出bug");
        productManager.receive(new Bug(productManager.chooseProgrammer()));
    }
    
    public void putProblem(){
        System.out.println( "業務員" + name + "提出線上問題");
        productManager.receive(new Problem(productManager.chooseProgrammer()));
    }

    public String getName() {
        return name;
    }

    public ProductManager getProductManager() {
        return productManager;
    }

    public void setProductManager(ProductManager productManager) {
        this.productManager = productManager;
    }
}

                這下一天的工作形式就變化了,小左將一天的工作寫成了下面這個樣子。

package com.command;

public class Work {

    public static void main(String[] args) {
        Programmer xiaozuo = new Programmer("小左");
        ProductManager productManager = new ProductManager(xiaozuo);
        
        Salesman salesmanA = new Salesman("A",productManager);
        Salesman salesmanB = new Salesman("B",productManager);
        Salesman salesmanC = new Salesman("C",productManager);
        Salesman salesmanD = new Salesman("D",productManager);
        
        salesmanA.putDemand();
        salesmanB.putDemand();
        salesmanB.putBug();
        salesmanC.putDemand();
        salesmanC.putProblem();
        salesmanD.putDemand();
        
        System.out.println("第一天產品經理分配任務");
        productManager.assign();
        productManager.printTaskList();
        System.out.println("第二天產品經理分配任務");
        productManager.assign();
        productManager.printTaskList();
    }
    
}

                “哈哈,這下業務人員徹底不認識我了,只認識產品經理,只不過由於公司就我一個程序員,所以產品經理那個平均的循環分配算法,分來分去還是分給我一個人了。不過這個也沒辦法,公司不招人,我也說了不算啊,而且小甜甜還沒熟悉好公司業務呢,也幫不了我。” 

                雖然最終這些任務都還是小左去做,但是這下小左不用加班了,一天最多四個任務,就算完不成臨時需要加班,那也是小左編碼速度的問題,心里相對會舒服很多,不至於太憋屈。畢竟自己沒完成,那加會兒班也會心甘情願。

                這會小左心情好了,心情一好,小左竟然開始動手畫起剛才的類圖來了。


                小左看着自己的作品,自言自語道:“雖然這個實際的類圖比命令模式的原版類圖稍微多了一層關系,就是ProductManager和Programmer的關聯關系,但是其它的可是和命令模式一模一樣啊,而且很明顯,業務人員(Salesman)和我們程序猿(Programmer)之間沒有關聯的關系了。”

                “看來我們公司應該招一個產品經理了啊,還得多招幾個程序猿,這樣我的工作就簡單的多了。公司和程序倒是挺相似的,程序是需要多個類之間相互協作,公司也是一樣啊,需要每個員工都分工協作,這樣就不需要一些人身兼數職了,簡直是累死個人啊!”

                “博客今天就先不寫了,稍微總結一下,明天好去給小甜甜說一下我今晚的發現,順便給她講講命令模式。這下又可以在她面前露一手了,估計這樣下去,她早晚會愛上我啊。啊,哈哈。。”小左又開始YY了。

                不過為了能給小甜甜留一個好印象,小左可是煞費心思啊,這不,他又開始總結起來了。

                “為了減輕自己的負擔,我添加了一個產品經理,並且還將任務抽象成類,這樣確實解決了我的很多問題,我得稍微總結下。”

                1,程序猿和業務員解耦,不直接打交道。

                2,產品經理分擔了程序猿的很多潛在任務,比如制定任務優先級,先做哪個后做哪個。

                3,程序猿不至於忘掉其中一個任務,因為產品經理那有任務列表的。

                4,任務有規划的完成,不至於加班或者說加班太頻繁。

               “不過要是給小甜甜解釋,這樣還不夠啊,得專業一點,才好忽悠啊。”小左暗暗的點頭道。

                用編程的語言來解釋命令模式的使用場景或者說可以解決的問題,就是下面幾點。

                1,希望將行為請求者和行為實現者解耦,不直接打交道。

                2,希望分離掉行為請求者一部分的責任,行為請求者只需要將命令發給調用者,不再主動的去讓行為實現者產生行為,符合單一職責原則。

                3,希望可以控制執行的命令列表,方便記錄,撤銷/重做以及事務等功能。

                4,期待可以將請求排隊,有序執行。

                5,希望可以將請求組合使用。

                “不過上面我總結的第五點在例子當中沒有體現,不過這個也好辦,其實就相當於是任務可以組合,比如一個線上的問題(Problem)很可能伴隨着一個bug(Bug),這樣的話可以做一個組合的任務,既要處理線上的問題同時也要處理Bug。”

                “裝X就要裝到底啊,命令模式的優點和缺點都有哪些,我也得給總結出來,別到時候在小甜甜面前掉鏈子。”小左一甩自己的中分頭說道。

                1,最大的優點,就是將行為請求者和行為實現者解耦。

                2,命令的添加特別方便,並且可以方便的制定各種命令和利用現有命令組合出新的命令。
                3,如果針對每一類具有共同接口的接受者制作一個調用者,可以控制命令的執行情況。

                “命令模式的缺點應該和大部分設計模式一樣,會增加系統的復雜性,這里的復雜性應該主要指的是類的數量,這個倒是好理解,看一下上面的例子就知道多了很多類。”

                “這下可以睡個安穩覺了,希望明天一舉拿下啊。啊哈。”說完,小左就躺床上進入了夢鄉。

                

              

                 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM