寫在前面
前面跟大家分享了裝飾者模式、觀察者模式、靜態工廠方法、工廠方法模式,今天跟大家分享下Android原始碼中的命令模式。
命令模式
定義
將一個請求封裝成一個物件,從而使你可用不同的請求對客戶進行引數化,對請求排隊或記錄請求日誌,以及支援可撤銷的操作。
使用場景
對於大多數請求——響應模式的功能,比較適合使用命令模式。
- 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動。
- 系統需要在不同的時間指定請求、將請求排隊(如:執行緒池+工作佇列)和執行請求。
- 系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作(比如系統掛掉之後重啟做一些恢復操作,還有資料庫的事務等)。
- 系統需要將一組操作組合在一起,即支援巨集命令。
結構
命令模式涉及到的角色:
- 客戶角色(Client):Client可以建立具體的命令物件,並且設定命令物件的接收者。Tips:不能把Clinet理解為我們平常說的客戶端,這裡的Client是一個組裝命令物件和接受者物件的角色,或者你把它理解為一個裝配者。
- 呼叫者角色(Invoker):負責呼叫命令物件執行請求,通常會持有命令物件(可以持有多個命令物件)。Invoker是Client真正觸發命令並要求命令執行相應操作的地方(使用命令物件的入口)。
- 命令角色(Command):定義命令的介面,宣告具體命令類需要執行的方法。這是一個抽象角色。
- 具體命令角色(ConcreteCommand):命令介面的具體實現物件,通常會持有接收者,並呼叫接收者的功能來完成命令要執行的操作。
- 接收者角色(Receiver):Receiver是真正執行命令的物件。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
實現
命令模式其實就是對命令進行封裝,將命令請求者和命令執行者的責任分離開來實現鬆耦合。 這裡我們通過一個餐廳點餐的例項來剖析一下命令模式:命令接收者Cook可以做各式各樣的菜,根據Waiter送過來的訂單來滿足顧客的需求,具體命令實現類PigCook執行做烤乳豬命令,DuckCook執行燒花鴨命令等等,Client負責組裝各個部分。
命令角色
1 2 3 4 5 |
public interface Command { public void execute(); public void undo(); public void redo(); } |
命令接收者
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Cook { //烤乳豬的方法 public void cookPig(){ } //燒花鴨的方法 public void cookDuck(){ } } |
具體命令角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
//做烤乳豬的命令 public class PigCook implements Command { private Cook mCook; public PigCook(Cook cook) { mCook = cook; } @Override public void execute() { mCook.cookPig(); } @Override public void undo() { } @Override public void redo() { } } //做燒花鴨的命令 public class DuckCook implements Command { private Cook mCook; public DuckCook(Cook cook) { mCook = cook; } @Override public void execute() { mCook.cookDuck(); } @Override public void undo() { } @Override public void redo() { } } |
呼叫者角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Waiter { private Command pig; private Command duck; public void setCommandPig(Command pig) { this.pig = pig; } public void setCommandDuck(Command duck) { this.duck = duck; } /** * 執行正常命令,這裡省略了undo和redo操作 */ public void invoke(int args) { //可以根據具體情況選擇執行某些命令 if(args == 0){ pig.execute(); }else if(args == 1){ duck.execute(); } } } |
客戶角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Client { /** * 組裝操作 */ public void assembleAction() { //建立一個命令接收者 Cook mCook = new Cook(); //建立一個命令的具體實現物件,並指定命令接收者 Command pig = new PigCook(mCook); Command duck = new DuckCook(mCook); Waiter mWaiter = new Waiter();//建立一個命令呼叫者 //為呼叫者指定烤乳豬命令物件 mWaiter.setCommandPig(pig); //為呼叫者指定燒花鴨命令物件 mWaiter.setCommandDuck(duck); //發起呼叫烤乳豬命令請求 mWaiter.invoke(0); //發起呼叫燒花鴨命令請求 mWaiter.invoke(1); } } |
可是,為什麼要這麼複雜咧,我只是想點個菜而已嘛,直接這麼搞不就好了?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Client { /** * 組裝操作 */ public void assembleAction() { //建立一個命令接收者 Cook mCook = new Cook(); //發起呼叫烤乳豬命令請求 mCook.cookPig(); //發起呼叫燒花鴨命令請求 mCook.cookDuck(); } } |
我們知道命令模式的一個優點是支援命令的撤銷(Undo)操作和恢復(Redo)操作,如果我們像上邊一樣呼叫,我們要想做撤銷是不是就不那麼方便了呢。同時還可以考慮下命令模式的其他幾個優點。
總結
- 每一個命令都是一個操作:請求的一方發出請求,要求執行一個操作;接收的一方收到請求,並執行操作。
- 命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的。
- 命令模式使請求本身成為一個物件,這個物件和其他物件一樣可以被儲存和傳遞。
- 命令模式的關鍵在於引入了抽象命令介面,且傳送者針對抽象命令介面程式設計,只有實現了抽象命令介面的具體命令才能與接收者相關聯。
Android原始碼中的命令模式
對於Android原始碼來說,Android底層邏輯對事件的轉發處理就用到了命令模式。Application Framework(應用程式框架層)中PackageManagerService類(包管理部分)也用到了命令模式。PackageManagerService是Android系統的Service之一,主要功能是實現對應用包的解析、管理、解除安裝等操作。我們來看下具體的結構。
HandlerParams是命令介面,即我們的Command角色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
private abstract class HandlerParams { private static final int MAX_RETRIES = 4; /** * Number of times startCopy() has been attempted and had a non-fatal * error. */ private int mRetries = 0; /** User handle for the user requesting the information or installation. */ private final UserHandle mUser; String traceMethod; int traceCookie; HandlerParams(UserHandle user) { mUser = user; } UserHandle getUser() { return mUser; } HandlerParams setTraceMethod(String traceMethod) { this.traceMethod = traceMethod; return this; } HandlerParams setTraceCookie(int traceCookie) { this.traceCookie = traceCookie; return this; } final boolean startCopy() { boolean res; try { if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this); if (++mRetries > MAX_RETRIES) { Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy(); res = true; } } catch (RemoteException e) { if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT"); mHandler.sendEmptyMessage(MCS_RECONNECT); res = false; } handleReturnCode(); return res; } final void serviceError() { if (DEBUG_INSTALL) Slog.i(TAG, "serviceError"); handleServiceError(); handleReturnCode(); } abstract void handleStartCopy() throws RemoteException; abstract void handleServiceError(); abstract void handleReturnCode(); } |
具體的包的安裝、移動以及包大小的測量分別在3個具體子類InstallParams、MoveParams和MeasureParams中實現。
而PackageHandler是Handler的子類,用來負責包相關訊息的處理,不同的請求對應不同的命令物件,然後通過命令物件來執行具體操作。
關於Receiver
通過接觸Android原始碼或者其他的一些原始碼,我們知道有些地方是沒有命令接收者(Receiver)這個角色的,這是為什麼呢?
個人認為,有的命令接收實現非常簡,可以直接用少量的程式碼來實現,沒有必要再增加類的數量。
參考連結:
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式