命令模式
命令模式(Command Pattern)是一種數據驅動的設計模式,它屬於行爲型模式。請求以命令的形式包裹在對象中,並傳給調用對象。調用對象尋找可以處理該命令的合適的對象,並把該命令傳給相應的對象,該對象執行命令。
以下的這個關鍵代碼放在最上面的原因在於想要更好的理解命令模式,我們需要明確的區分這三部分
關鍵代碼
定義三個角色:1、received 真正的命令執行對象 2、Command 3、invoker 使用命令對象的入口
我在舉例後會給大家區分這個的
介紹
意圖:將一個請求封裝成一個對象,從而使您可以用不同的請求對客戶進行參數化。
主要解決:在軟件系統中,行爲請求者與行爲實現者通常是一種緊耦合的關係,但某些場合,比如需要對行爲進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適。
何時使用:在某些場合,比如要對行爲進行"記錄、撤銷/重做、事務"等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將"行爲請求者"與"行爲實現者"解耦?將一組行爲抽象爲對象,可以實現二者之間的鬆耦合。
如何解決:通過調用者調用接受者執行命令,順序:調用者→接受者→命令。
優點: 1、降低了系統耦合度。 2、新的命令可以很容易添加到系統中去。
缺點:使用命令模式可能會導致某些系統有過多的具體命令類。
使用場景:認爲是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個按鈕都是一條命令。 2、模擬 CMD。
注意事項:系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作,也可以考慮使用命令模式,見命令模式的擴展。
例子
在大學裏,輔導員經常會發一些公告或文件在班長羣裏,然後再由班長轉發或執行,很多通知班長僅需要在第一時間轉發到班羣即可。
如上圖,班長工作大部分是接收輔導員的通知再將通知轉給同學,而且大多數情況下不需要關心文件或通知內容(有時候還是需要關心一下的),只需要將通知從班委羣轉到班級羣即可。
對應的命令模式:
首先客戶端需要創建一個命令對象,並將命令對象存儲在調用者中,一個命令加載到調用者後,該命令可以被使用後丟棄(如:班長通知五一放假注意安全),也可以重複使用(如:班長髮布獎學金公示,需要重複發送確認)。
定義:一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。命令對象將動作和接收者包裝進了對象中,這個對象只暴露一個execute()方法,當方法被調用時,接收者會執行命令封裝的動作。在使用者看來,並不需要接收者進行了哪些動作,只知道調用一下execute()方法,目的就可以達到。
以上有點抽象 我們用代碼來理解
先定義一個命令接口,所有命令實現此接口。
/**
* 通知
*/
public interface Command {
void execute();
}
這裏簡單的定義一個學生羣體,作爲命令接收者。
/**
* 一羣可愛的學生
*/
public class Students {
/**
* 開班會
*/
public void meeting(){
System.out.println("教學樓四樓開班會....");
}
/**
* 參加講座
*/
public void attendLecture(){
System.out.println("講堂羣參加講座....");
}
/**
* 提交材料
*/
public void submitMaterial(){
System.out.println("提交材料....");
}
//............
}
如果想要實現這個命令就需要將接收者傳入命令,這裏學生羣體還可以擴展修改爲其他班級學生,因爲一個輔導員所下發通知是相同的。
/**
* 開會通知
*/
public class MeetCommand implements Command{
private Students students;
public MeetCommand(Students students) {
this.students = students;
}
@Override
public void execute() {
students.meeting();
}
}
/**
* 講座通知
*/
public class LectureCommand implements Command{
private Students students;
public LectureCommand(Students students) {
this.students = students;
}
@Override
public void execute() {
students.attendLecture();
}
}
/**
* 提交材料通知
*/
public class SubmitMaterialCommand implements Command{
private Students students;
public SubmitMaterialCommand(Students students) {
this.students = students;
}
@Override
public void execute() {
students.submitMaterial();
}
}
/**
* 班長(調用者)
*/
public class Monitor {
Command command;
public Monitor() {}
public void setCommand(Command command) {
this.command = command;
}
// 發佈通知
public void releasingNotice(){
command.execute();
}
}
/**
* 輔導員(客戶)
*/
public class Counsellor {
public static void main(String[] args) {
// 一班
Monitor monitorOne = new Monitor();
Students studentsOne = new Students();
LectureCommand lectureCommandOne = new LectureCommand(studentsOne);
// 二班
Monitor monitorTwo = new Monitor();
Students studentsTwo = new Students();
LectureCommand lectureCommandTwo = new LectureCommand(studentsTwo);
MeetCommand meetCommandTwo = new MeetCommand(studentsTwo);
// 一班聽講座
monitorOne.setCommand(lectureCommandOne);
monitorOne.releasingNotice();
// 二班聽講座,聽完留下來看班會
monitorTwo.setCommand(lectureCommandTwo);
monitorTwo.releasingNotice();
monitorTwo.setCommand(meetCommandTwo);
monitorTwo.releasingNotice();
}
}
如上,客戶端創建調用者和接收者並把命令傳遞給調用者,在調用者看來每次得到命令後只需要execute一下就行了。後面無論什麼通知,需要實現Command,就可以是一個正常通知。命令對象只提供execute方法,當方法被調用時,接收者就可以進行行爲動作,達到請求目的。通過命令對象調用者和接收者之前可以達到間接解耦。
其實這段代碼 就是 我剛纔最最最開始提及的,一定要區分那三個,才能更好的理解
received 真正的命令執行對象 就是 學生
command 就不用說了 就是command
nvoker 使用命令對象的入口 就是班長
然後你在看下代碼看下能更好的區分不
命令模式其他用途
隊列:
在Java線程池中,線程的調度基於生產者消費者模式,所以往往在內部會實現一個阻塞隊列。可以在一端添加任務,另一端消費任務。同時任務和工作隊列之前是解耦的,無論傳入什麼任務給線程池,工作隊列只負責取出任務,執行execute()方法,下一個…。
保留日誌:
Redis是現在常用的緩存數據庫,讀寫快的一個原因是數據不走磁盤,都是基於內存讀寫。雖然都是基於內存,但同樣提供持久化方案。其中一種叫AOF,相對於RDB來說它是以執行命令逐條追加的方式來持久化的。所以類似Redis,在開發過程中,命令模式也可用於逐條持久化,因爲很多應用在進行數據修改後不是立即持久化到磁盤中,而是先放入內存,等待時機寫入磁盤,所以數據變更沒發快速存儲。如果在使用命令模式時,執行完請求將其持久化到磁盤,那麼在將來某一時刻即使宕機,只要從磁盤中取出命令逐條執行就行(Redis的AOF也是這樣恢復的)。