前言
個人感覺,該模式主要還是在多線程程序的設計中比較常用,尤其是一些異步任務執行的過程。但是本文還是打算先在單線程程序裏總結它的用法,至於多線程環境中命令模式的用法,還是想在多線程的設計模式裏重點總結。
實現思路
其實思路很簡單,就是把方法的請求調用和具體執行過程分開,讓客戶端不知道該請求是如何、何時執行的。那麼如何分開呢?
其實沒什麼複雜的,就是使用 OO 思想,把對方法的請求封裝爲對象即可,然後在設計一個請求的接受者對象,當然還要有一個請求的發送者對象,請求本身也是一個對象。最後,請求要如何執行呢?
故,除了請求對象,請求發送者,請求接受者,還要一個請求執行者——這裏可以看成是客戶端,而請求(其實叫命令、或者請求都是一樣的意思,後文就用請求這個術語)最好設計爲抽象的(或者接口)。
也可得知,命令模式是對象的行爲型的設計模式。
簡單的命令模式
模擬場景:在線教育平臺售賣一些培訓的視頻課程,規定必須付費後才能觀看,故管理員需要有開放課程觀看和關閉課程觀看權限的操作
首先需要一個抽象的命令(請求)接口
public interface ICommand { // 抽象的命令(請求)接口
void execute();
}
然後設計一個課程類——Lesson,它代表課程本身,也是命令(請求)的接受者,因爲是對課程這個實體下命令
public class Lesson { // 代表課程本身,也是命令(請求)的接受者,因爲是對課程這個實體下命令
private String name;
public Lesson(String name) {
this.name = name;
}
public void openLesson() {
System.out.println("可以觀看課程:" + name);
}
public void closeLesson() {
System.out.println("不可以觀看課程:" + name);
}
}
下面是兩個具體的命令類,分別實現命令接口,裏面是有聚合關係,把課程 Lesson 的引用聚合到命令類,哪一個命令要對哪一個實體,不能寫錯,比如關閉對關閉。
public class CloseCommand implements ICommand {
private Lesson lesson;
public CloseCommand(Lesson lesson) {
this.lesson = lesson;
}
@Override
public void execute() {
this.lesson.closeLesson();
}
}
//////////////////////////////////////////////////////
public class OpenCommand implements ICommand {
private Lesson lesson;
public OpenCommand(Lesson lesson) {
this.lesson = lesson;
}
@Override
public void execute() {
this.lesson.openLesson();
}
}
設計一個管理員類,作爲命令(請求)的調用者,用來發出請求(命令),而命令的實際執行,交給了命令(請求)的接受者——Lesson
public class Admin2 {
private ICommand commond;
public void setCommond(ICommand commond) {
this.commond = commond;
}
public void executeCommond() {
this.commond.execute();
}
}
客戶端
Lesson lesson1 = new Lesson("c++"); // 請求(命令)的接受者
CloseCommand closeCommand1 = new CloseCommand(lesson1); // 命令封裝爲對象
OpenCommand openCommand1 = new OpenCommand(lesson1);
Admin2 admin2 = new Admin2(); // 請求(命令)的調用者:用來發出請求
admin2.setCommond(openCommand1); // 將命令傳給調用者
admin2.executeCommond(); // 發出請求(命令),但是admin 並不知道這個請求(命令)發給了誰,是誰在執行這個請求(命令)
admin2.setCommond(closeCommand1);
admin2.executeCommond();
如上就實現了請求調用和具體執行的分離(解耦)
一次執行多個命令
下面是一次執行多個命令的寫法,也可以作爲宏命令的實現
命令接口和具體命令都不變,admin 變化如下:
public class Admin {
private List<ICommand> commondList = new ArrayList<>(); // 使用 ArrayList 還能保證命令的順序執行
public void addCommond(ICommand commond) {
commondList.add(commond);
}
public void executeCommond() {
for (ICommand commond : commondList) {
commond.execute();
}
commondList.clear();
}
}
當然這裏用棧等數據結構去包裝命令也是可以的
Lesson lesson = new Lesson("java"); // 請求(命令)的接受者
CloseCommand closeCommand = new CloseCommand(lesson); // 命令
OpenCommand openCommand = new OpenCommand(lesson);
Admin admin = new Admin(); // 請求(命令)的調用者:用來發出請求
admin.addCommond(openCommand); // 將命令傳給調用者
admin.addCommond(closeCommand);
admin.executeCommond();
引申:空類型模式
再比如,使用靜態數組去包裝命令,這裏引申一個空類型模式,就是說有一個類,這個類什麼都不做,就是佔位或者初始化用的,代替 null 類型。
下面舉一個例子,設計一個控制器,控制電燈的開關,閃爍,變暗,變亮等操作
public interface ICommand2 {
void execute(); // 命令接口
}
//////////////////////////////////
public class LightOffCommand implements ICommand2 {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.off();
}
}
//////////////////////////////////
public class LightOnCommand implements ICommand2 {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.on();
this.light.zoomin();
this.light.blink();
}
}
//////////////////////////////////
public class EmptyCommand implements ICommand2 { // 空類型模式的體現
@Override
public void execute() {
System.out.println("什麼都不做");
}
}
//////////////////////////////////
public class Light {
public Light() {
}
public void on() {
System.out.println("電燈打開");
}
public void off() {
System.out.println("電燈關閉");
}
public void zoomin() {
System.out.println("燈光變強");
}
public void zoomout() {
System.out.println("燈光變弱");
}
public void blink() {
System.out.println("燈光閃爍");
}
public void noBlink() {
System.out.println("燈光停止閃爍");
}
}
下面是一個控制器類,setCommand 方法可以設置某個命令和某個操作的對應關係,初始化時,使用空類型模式
public class MainController {
private ICommand2[] onCommands;
private ICommand2[] offCommands;
public MainController() {
this.onCommands = new ICommand2[3];
this.offCommands = new ICommand2[2];
ICommand2 emptyCommand = new EmptyCommand();
for (int i = 0; i < 3; i++) {
this.onCommands[i] = emptyCommand;
}
for (int i = 0; i < 2; i++) {
this.offCommands[i] = emptyCommand;
}
}
public void setCommand(int idx, ICommand2 onCommand, ICommand2 offCommand) {
this.onCommands[idx] = onCommand;
this.offCommands[idx] = offCommand;
}
public void executeOnCommand(int idx) {
this.onCommands[idx].execute();
}
public void executeOffCommand(int idx) {
this.offCommands[idx].execute();
}
}
客戶端
MainController mainController = new MainController();
Light roomLight = new Light();
Light doorLight = new Light();
LightOnCommand roomLightOnCommand = new LightOnCommand(roomLight);
LightOffCommand roomLightOffCommand = new LightOffCommand(roomLight);
LightOnCommand doorLightOnCommand = new LightOnCommand(doorLight);
LightOffCommand doorLightOffCommand = new LightOffCommand(doorLight);
mainController.setCommand(0, roomLightOnCommand, roomLightOffCommand);
mainController.setCommand(1, doorLightOnCommand, doorLightOffCommand);
mainController.executeOnCommand(0);
mainController.executeOffCommand(0);
mainController.executeOnCommand(1);
mainController.executeOffCommand(1);
mainController.executeOnCommand(2);
命令模式在單線程環境下的優點(使用場景)
通過封裝對方法的請求調用和方法執行過程,並將其分離,也就是所謂的完全解耦了。
故可以對方法的調用執行實現一些額外操作,比如記錄日誌,撤銷某個方法的請求調用,或者實現一次請求,N 次執行某個方法等。
在架構上,可以讓程序易於擴展新的請求(命令)。
命令模式在多線程程序中的優點
這樣做,在多線程環境下的好處是:
1、避免算法(策略)模塊執行緩慢拖累調用方——抽象了需要等待的操作
2、控制執行順序,因爲請求調用和具體執行分離,故執行順序和調用順序沒有關係
3、可以輕鬆實現請求的取消,或者反覆執行某個請求
4、請求調用和具體執行分離後,進一步把負責調用的機器和負責執行的機器分開,可以基於網絡,實現分佈式程序
命令的撤銷實現
前面,無論在什麼環境下,都提到了能撤銷命令(請求),故命令模式經常和備忘錄模式搭配使用。參考:保存快照和撤銷功能的實現方案——備忘錄模式總結。
這裏舉一個很簡單的例子,還是電燈開關的例子
public interface ICommand3 {
void execute();
void undo(); // 和 execute 執行相反的操作
}
//////////////////////////////////
public class EmptyCommand implements ICommand3 {
@Override
public void execute() {
System.out.println("什麼都不做");
}
@Override
public void undo() {
System.out.println("什麼都不做");
}
}
/////////////////////////////////
public class LightOnCommand implements ICommand3 {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.on();
this.light.zoomin();
this.light.blink();
}
@Override
public void undo() {
this.light.noBlink();
this.light.zoomout();
this.light.off();
}
}
///////////////////////////////////
public class LightOffCommand implements ICommand3 {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
this.light.off();
}
@Override
public void undo() {
this.light.on();
}
}
控制器也要變化,初始化命令的同時,也要初始化 undo 命令
public class MainController {
private ICommand3[] onCommands;
private ICommand3[] offCommands;
private ICommand3 undoCommand; // 記錄上一個命令
public MainController() {
this.onCommands = new ICommand3[3];
this.offCommands = new ICommand3[2];
ICommand3 emptyCommand = new EmptyCommand();
for (int i = 0; i < 3; i++) {
this.onCommands[i] = emptyCommand;
}
for (int i = 0; i < 2; i++) {
this.offCommands[i] = emptyCommand;
}
this.undoCommand = emptyCommand; // 初始化 undo 命令
}
public void setCommand(int idx, ICommand3 onCommand, ICommand3 offCommand) {
this.onCommands[idx] = onCommand;
this.offCommands[idx] = offCommand;
}
public void executeOnCommand(int idx) {
this.onCommands[idx].execute();
this.undoCommand = this.onCommands[idx];
}
public void executeOffCommand(int idx) {
this.offCommands[idx].execute();
this.undoCommand = this.offCommands[idx];
}
public void undoCommand() {
this.undoCommand.undo();
}
}
客戶端
MainController mainController = new MainController();
Light roomLight = new Light();
LightOffCommand offCommand = new LightOffCommand(roomLight);
LightOnCommand onCommand = new LightOnCommand(roomLight);
mainController.setCommand(0, onCommand, offCommand);
mainController.executeOnCommand(0);
System.out.println();
mainController.executeOffCommand(0);
System.out.println();
mainController.undoCommand();
System.out.println();
mainController.executeOffCommand(0);
System.out.println();
mainController.executeOnCommand(0);
System.out.println();
mainController.undoCommand();
打印如下
電燈打開
燈光變強
燈光閃爍
電燈關閉
電燈打開
電燈關閉
電燈打開
燈光變強
燈光閃爍
燈光停止閃爍
燈光變弱
電燈關閉
命令模式的缺陷
個人覺得,唯一的缺點就是會使得程序複雜性提高,但是我認爲微不足道,基礎紮實的 RD 應該無壓力閱讀和使用纔對,因爲在多線程程序裏,該模式大量出現,比如 Netty 等框架就大量使用了該思想。
命令模式和策略模式的區別
策略是不同的算法做同一件事情。不同的策略之間可以相互替換。比如實現一個支付功能,有微信支付,支付寶支付,各自渠道的支付。。。
命令是不同的命令做不同的事情。對外隱藏了具體的執行細節。比如菜單中的複製,移動和壓縮
JDK 中的命令模式
最最常見的就是 lang 包裏的 Runnable 接口,這就是一個命令接口,將對線程啓動的請求和具體的執行分離了。實現該接口,也是啓動線程推薦的寫法
歡迎關注
dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!
原文出處:https://www.cnblogs.com/kubixuesheng/p/10353809.html