26. 命令模式

定義

命令模式(Command Pattern):將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而讓我們可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或者記錄請(qǐng)求日志,以及支持可撤銷的操作。其別名為動(dòng)作(Action)模式或事務(wù)(Transaction)模式。

通俗理解

我們都有找過(guò)10086的客服,如果你的手機(jī)出什么問(wèn)題了,都會(huì)撥打這個(gè)號(hào)碼,請(qǐng)求客服的解決。通常請(qǐng)求客服的時(shí)候,你都會(huì)說(shuō)明一下你的問(wèn)題,是流量問(wèn)題、還是話費(fèi)問(wèn)題,然后客服會(huì)根據(jù)你的這些問(wèn)題,進(jìn)行解決,然后你就很愉快地掛了電話。

實(shí)際上卻沒(méi)有這么簡(jiǎn)單,如果一個(gè)人打電話過(guò)來(lái)了,說(shuō)他們家的小區(qū)出現(xiàn)了“乒乓效應(yīng)”[1],老是斷線。估計(jì)客服聽(tīng)到這個(gè)詞都會(huì)一臉懵逼,不知道怎么處理吧。再或者,有一個(gè)人的問(wèn)題是他的流量充值了,但是還沒(méi)有到賬,客服查明之后往這個(gè)人的賬戶上沖流量,結(jié)果一個(gè)手抖,多打了兩個(gè)零,系統(tǒng)還不支持撤銷,遇到這種情況又得扣工資。還有... ...

可見(jiàn),客服也不容易。那么怎么樣可以讓客服更好地進(jìn)行服務(wù),減少犯錯(cuò)的幾率呢?我們保持一個(gè)觀點(diǎn)就好:專業(yè)的事情讓專業(yè)的人去做。這時(shí)候,客服只是一個(gè)客服系統(tǒng)的門(mén)面,當(dāng)客服接聽(tīng)到電話的時(shí)候,他會(huì)將問(wèn)題轉(zhuǎn)發(fā)給專業(yè)的人士,例如流量問(wèn)題就讓流量組的人去處理、基站問(wèn)題就讓設(shè)備維護(hù)人員去處理;第二個(gè)是,專業(yè)人士有相關(guān)的撤銷方法,如果手抖了輸多了一個(gè)零,可以撤銷這個(gè)請(qǐng)求,中午還能加雞腿??,當(dāng)然,用戶不滿意的時(shí)候,還可以重復(fù)用戶請(qǐng)求,把他的要求再做一遍。

這個(gè)就是命令模式。用戶(Client)通過(guò)和客服(Invoker)交流,客服根據(jù)不同的問(wèn)題,將這些問(wèn)題轉(zhuǎn)發(fā)到不同的組(Command),然后相應(yīng)的組的成員去完成這個(gè)工作。

示例

客服系統(tǒng)作為示例。

渣渣程序

話費(fèi)服務(wù)和流量服務(wù)

public class TrafficService {
    public void answer() {
        System.out.println("流量服務(wù)");
    }
}
public class PhoneChargeService {
    public void answer() {
        System.out.println("話費(fèi)服務(wù)");
    }
}

客服

public class CustomerService {
    public void service(String type) {
        switch (type){
            case "phoneCharge":
                new PhoneChargeService().answer();
                break;
            case "traffic":
                new TrafficService().answer();
                break;
            default:
                System.out.println("服務(wù)不支持");
                break;
        }
    }
}

程序主入口

public class Main {
    public static void main(String[] args) {
        CustomerService service = new CustomerService();
        service.service("phoneCharge");
    }
}
//話費(fèi)服務(wù)

缺點(diǎn)顯而易見(jiàn):

  1. 客服需要知道每一種問(wèn)題的處理方法,即使TrafficService是其他客服,門(mén)面的客服也需要知道得調(diào)用TrafficService什么方法;
  2. 新增加處理類型需要修改CustomerService.service(String type)方法,違反開(kāi)閉原則;
  3. 沒(méi)有撤銷。

優(yōu)化

類圖

image

程序

話費(fèi)服務(wù)和流量服務(wù)

public class PhoneChargeService {
    public void answer() {
        System.out.println("話費(fèi)服務(wù)");
    }
    public void undo() {
        System.out.println("撤回話費(fèi)服務(wù)");
    }
}
//TrafficService類似,省略

命令接口和實(shí)現(xiàn)

public interface ICommand {
    /**
     * 執(zhí)行
     */
    void execute();

    /**
     *  撤銷
     */
    void undo();

    /**
     * 重做
     */
    void redo();
}
public class PhoneChangeCommand implements ICommand {
    private PhoneChargeService phoneChargeService;
    private boolean change;
    // 構(gòu)造器,getter和setter方法
    public void execute() {
        phoneChargeService.answer();
        change = true;
    }
    public void undo() {
        if(change) {
            phoneChargeService.undo();
        } else {
            System.out.println("沒(méi)有操作話費(fèi)服務(wù),撤銷失敗");
        }
    }
    public void redo() {
        execute();
    }
}
//TrafficCommand類似,省略

客服

public class Invoker {
    private List<ICommand> commands = new ArrayList<>();

    public List<ICommand> getCommands() {
        return commands;
    }

    public void setCommands(List<ICommand> commands) {
        this.commands = commands;
    }

    public void execumentCommand(ICommand command) {
        commands.add(command);
        command.execute();
    }

    public void redoCommand() {
        if(commands.size() > 0) {
            commands.get(commands.size()-1).redo();
            commands.add(commands.get(commands.size()-1));
        } else {
            System.out.println("任務(wù)列表當(dāng)中無(wú)任務(wù),不能撤銷");
        }
    }

    public void undoCommand(){
        if(commands.size() > 0) {
            commands.get(commands.size()-1).undo();
            commands.remove(commands.size()-1);
        } else {
            System.out.println("沒(méi)有服務(wù)可以撤銷");
        }
    }
}

主入口

public class Main {
    public static void main(String[] args) {
        ICommand phoneCommand = new PhoneChangeCommand(new PhoneChargeService());
        ICommand trafficCommand = new TrafficCommand(new TrafficService());

        Invoker server = new Invoker();
        server.execumentCommand(phoneCommand);
        server.execumentCommand(trafficCommand);
        server.redoCommand();

        server.undoCommand();
        server.undoCommand();
        server.undoCommand();
        server.undoCommand();
    }
}
//話費(fèi)服務(wù)
//流量服務(wù)
//流量服務(wù)
//撤回流量服務(wù)
//撤回流量服務(wù)
//撤回話費(fèi)服務(wù)
//沒(méi)有服務(wù)可以撤銷

優(yōu)點(diǎn)

  1. 如果需要添加一個(gè)新的服務(wù)類型,不需要修改任何的內(nèi)容,只需要添加一個(gè)服務(wù)的處理類,一個(gè)命令實(shí)現(xiàn)類實(shí)現(xiàn)命令接口,并在這個(gè)命令實(shí)現(xiàn)類里面注入服務(wù)的處理類即可,符合開(kāi)閉原則;
  2. 請(qǐng)求和接收解耦,Invoker通過(guò)ICommand調(diào)用PhoneChargeService,而Main則調(diào)動(dòng)Invoker去調(diào)用服務(wù)方法;
  3. 容易設(shè)計(jì)出一個(gè)命令隊(duì)列或者組合命令;
  4. 撤銷undo和恢復(fù)redo。

缺點(diǎn)

  1. 類?的真多。

應(yīng)用場(chǎng)景

  1. 請(qǐng)求與調(diào)用的解耦,就像10086的客服一樣,我只需要知道我的問(wèn)題解決了就可以,不需要知道這個(gè)過(guò)程,另外相關(guān)的維護(hù)組的人員也只需要知道有工作要做就行,不需要知道這個(gè)工作室誰(shuí)派下來(lái)的;
  2. 需要支持撤銷和恢復(fù)的操作;
  3. 需要將命令組合在一起。

實(shí)例

JDK Awt和Swing包,MenuItem類

代碼

e26_command_pattern

http://www.itdecent.cn/p/8be50cac2929


  1. 通信術(shù)語(yǔ),指的是手機(jī)在兩個(gè)基站覆蓋范圍的中間地帶,手機(jī)不停切換對(duì)這兩個(gè)基站的連接,造成信號(hào)的丟失,一般的解決辦法是加大基站的功率,使得基站的覆蓋范圍更廣,或者是增加基站的數(shù)量。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容