本文節選自《設計模式就該這樣學》
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彈架構 』可獲取更多技術乾貨!