本文節選自《設計模式就該這樣學》
1 命令模式的UML類圖
命令模式的UML類圖如下圖所示。

2 使用命令模式重構播放器控制條
假如我們開發一個播放器,播放器有播放功能、拖動進度條功能、停止播放功能、暫停功能,我們在操作播放器的時候並不是直接調用播放器的方法,而是通過一個控制條去傳達指令給播放器內核,具體傳達什么指令,會被封裝為一個個按鈕。那么每個按鈕就相當於對一條命令的封裝。用控制條實現了用戶發送指令與播放器內核接收指令的解耦。下面來看代碼,首先創建播放器內核GPlayer類。
public class GPlayer {
public void play(){
System.out.println("正常播放");
}
public void speed(){
System.out.println("拖動進度條");
}
public void stop(){
System.out.println("停止播放");
}
public void pause(){
System.out.println("暫停播放");
}
}
創建命令接口IAction類。
public interface IAction {
void execute();
}
然后分別創建操作播放器可以接收的指令,播放指令PlayAction類的代碼如下。
public class PlayAction implements IAction {
private GPlayer gplayer;
public PlayAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.play();
}
}
暫停指令PauseAction類的代碼如下。
public class PauseAction implements IAction {
private GPlayer gplayer;
public PauseAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.pause();
}
}
拖動進度條指令SpeedAction類的代碼如下。
public class SpeedAction implements IAction {
private GPlayer gplayer;
public SpeedAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.speed();
}
}
停止播放指令StopAction類的代碼如下。
public class StopAction implements IAction {
private GPlayer gplayer;
public StopAction(GPlayer gplayer) {
this.gplayer = gplayer;
}
public void execute() {
gplayer.stop();
}
}
最后創建控制條Controller類。
public class Controller {
private List<IAction> actions = new ArrayList<IAction>();
public void addAction(IAction action){
actions.add(action);
}
public void execute(IAction action){
action.execute();
}
public void executes(){
for(IAction action : actions){
action.execute();
}
actions.clear();
}
}
從上面代碼來看,控制條可以執行單條命令,也可以批量執行多條命令。下面來看客戶端測試代碼。
public static void main(String[] args) {
GPlayer player = new GPlayer();
Controller controller = new Controller();
controller.execute(new PlayAction(player));
controller.addAction(new PauseAction(player));
controller.addAction(new PlayAction(player));
controller.addAction(new StopAction(player));
controller.addAction(new SpeedAction(player));
controller.executes();
}
由於控制條已經與播放器內核解耦了,以后如果想擴展新命令,只需增加命令即可,控制條的結構無須改動。
3 命令模式在JDK源碼中的應用
首先來看JDK中的Runnable接口,Runnable相當於命令的抽象,只要是實現了Runnable接口的類都被認為是一個線程。
public interface Runnable {
public abstract void run();
}
實際上調用線程的start()方法之后,就有資格去搶CPU資源,而不需要編寫獲得CPU資源的邏輯。而線程搶到CPU資源后,就會執行run()方法中的內容,用Runnable接口把用戶請求和CPU執行進行解耦。
4 命令模式在JUnit源碼中的應用
再來看一個大家非常熟悉的junit.framework.Test接口。
package junit.framework;
public interface Test {
public abstract int countTestCases();
public abstract void run(TestResult result);
}
Test接口中有兩個方法,第一個是countTestCases()方法,用來統計當前需要執行的測試用例總數。第二個是run()方法,用來執行具體的測試邏輯,其參數TestResult是用來返回測試結果的。實際上,我們在平時編寫測試用例的時候,只需要實現Test接口就被認為是一個測試用例,那么在執行的時候就會被自動識別。通常做法都是繼承TestCase類,不妨來看一下TestCase的源碼。
public abstract class TestCase extends Assert implements Test {
...
public void run(TestResult result) {
result.run(this);
}
...
}
實際上,TestCase類也實現了Test接口。我們繼承TestCase類,相當於也實現了Test接口,自然就會被掃描成為一個測試用例。
關注微信公眾號『 Tom彈架構 』回復“設計模式”可獲取完整源碼。
本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!
