Java設(shè)計(jì)模式之命令模式
本文通過解決老王經(jīng)常搞錯(cuò)借書人的問題,來引出行為型模式中的命令模式。為了在案例之上理解的更加透徹,我們需要了解命令模式在源碼中的應(yīng)用。最后指出命令模式的應(yīng)用場景和優(yōu)缺點(diǎn)。
讀者可以拉取完整代碼到本地進(jìn)行學(xué)習(xí),實(shí)現(xiàn)代碼均測試通過后上傳到碼云,本地源碼下載。
一、引出問題
老王的書房藏書越來越多,每天來借書的人絡(luò)繹不絕。每天有人借書、還書、老王將A借的書算到B頭上的烏龍事件頻出。老王和小王就商量著手解決這個(gè)問題。
小王提議,在老王和借書者之間再增加一個(gè)“記錄員”角色,記錄員只管報(bào)名字就行了,具體是借什么書由借書者自己決定就好了。
老王說:這能解決部分問題。但在真實(shí)的場景下,不可能來一個(gè)借書者“記錄員”就跑一趟。而且借書者有時(shí)候會借一半臨時(shí)有事就不借了。這些問題你也要考慮進(jìn)去。
老王接著說:你應(yīng)該,在“記錄員”角色中,增加一個(gè)隊(duì)列,將所有借書者都放到一個(gè)隊(duì)列中,既有往隊(duì)列中放命令的方法,也有從命令中移除的方法,方便“記錄員”請求排隊(duì)和“撤銷”。
二、命令模式的概念和應(yīng)用
老王提出來的正是命令模式的“白話文解釋”。我們來看命令模式的官方概念:將一個(gè)請求封裝為一個(gè)對象,使發(fā)出請求的責(zé)任和執(zhí)行請求的責(zé)任分割開,解耦合。這樣兩者之間通過命令對象進(jìn)行溝通,這樣方便將命令對象進(jìn)行存儲、傳遞、調(diào)用、增加與管理。
在命令模式中有三個(gè)角色:
抽象命令類(Command)角色: 定義命令的接口,聲明執(zhí)行的方法。
實(shí)現(xiàn)者/接收者(Receiver)(老王)角色: 接收者,真正執(zhí)行命令的對象。任何類都可能成為一個(gè)接收者,只要它能夠?qū)崿F(xiàn)命令要求實(shí)現(xiàn)的相應(yīng)功能。
具體命令(Concrete Command)(記錄員)角色:具體的命令,實(shí)現(xiàn)命令接口;通常會持有接收者,并調(diào)用接收者的功能來完成命令要執(zhí)行的操作。
我們基于概念和角色劃分,實(shí)現(xiàn)代碼:
抽象命令類:
/** * 抽象命令類 * @author tcy * @Date 25-08-2022 */ public interface AbstractCommand { //只需要定義一個(gè)統(tǒng)一的執(zhí)行方法 void execute(); }
具體命令角色(老王):
/** * 具體命令 * @author tcy * @Date 25-08-2022 */ public class ConcreteCommand implements AbstractCommand { //持有接受者對象 private String clent; public ConcreteCommand(String clent){ this.clent = clent; } @Override public void execute() { System.out.println("具體執(zhí)行者角色(老王):"+clent+"借書..."); } }
接收者(記錄員):
/** * 接收者 * @author tcy * @Date 25-08-2022 */ public class ReceiverCommand { //可以持有很多的命令對象 private ArrayList<AbstractCommand> commands; public ReceiverCommand() { commands = new ArrayList(); } public void setCommand(AbstractCommand cmd){ commands.add(cmd); } public void removeCommand(AbstractCommand cmd){ commands.remove(cmd); } // 發(fā)出命令 public void borrowBookMeaaage() { System.out.println("接受者角色(記錄員):有人來借書啦..."); //通知全部命令 for (int i = 0; i < commands.size(); i++) { AbstractCommand cmd = commands.get(i); if (cmd != null) { cmd.execute(); } } } }
客戶端(借書者):
/** * @author tcy * @Date 25-08-2022 */ public class Client { public static void main(String[] args) { //創(chuàng)建接收者 //將訂單和接收者封裝成命令對象 ConcreteCommand cmd1 = new ConcreteCommand( "A"); ConcreteCommand cmd2 = new ConcreteCommand( "B"); //創(chuàng)建具體命令者 ReceiverCommand invoker = new ReceiverCommand(); invoker.setCommand(cmd1); invoker.setCommand(cmd2); //喊一聲有人要借書 invoker.borrowBookMeaaage(); } }
基于命令模式實(shí)現(xiàn)的代碼就實(shí)現(xiàn)了,但是看懂代碼是一回事,自己能寫出來就是另外一回事了。讀者最好根據(jù)案例重新仿寫一遍。
三、源碼中的應(yīng)用
在源碼中使用命令模式的典型案例就是Jdk多線程章節(jié)中的Runnable ,Runnable 相當(dāng)于命令模式中的抽象命令角色。Runnable 中的 run() 方法就當(dāng)于 execute() 方法。
我們知道,Java中一個(gè)類實(shí)現(xiàn)Runnable 接口,那么該類就認(rèn)為是一個(gè)線程,就相當(dāng)于命令模式中的具體命令角色。
當(dāng)我們調(diào)用start()方法后,就可以與別的線程強(qiáng)占CPU的資源,在占用CPU的線程中就會執(zhí)行run()方法。CPU的調(diào)度者就相當(dāng)于具體命令角色也即記錄員。Runnable 就完美的實(shí)現(xiàn)了用戶自定義線程和CPU的解耦合。
命令模式在Runnable 中的應(yīng)用應(yīng)該很好理解。
四、總結(jié)
優(yōu)點(diǎn)很明顯,解耦了命令請求與實(shí)現(xiàn),很容易的可以增加新命令,支持命令隊(duì)列。
但是,這樣會不可避免的使具體命令類過多,增加了理解上的困難。
設(shè)計(jì)模式學(xué)到這種程度,我們就會發(fā)現(xiàn)設(shè)計(jì)模式不是一種單一的技術(shù),而是各種技術(shù)的綜合體。
我們在學(xué)習(xí)設(shè)計(jì)模式的時(shí)候一定不要僅局限于一種模式,而是站在一定的高度去整體衡量哪種設(shè)計(jì)模式才是最優(yōu)的。
有時(shí)候我們會發(fā)現(xiàn),使用設(shè)計(jì)模式會讓我們的代碼變得更加的復(fù)雜,但以自己目前的開發(fā)經(jīng)驗(yàn)又不能確定是否采用設(shè)計(jì)模式是一個(gè)好的選擇。
歸根結(jié)低,還是我們對設(shè)計(jì)模式掌握的不夠熟練,這就需要我們繼續(xù)深入學(xué)習(xí)設(shè)計(jì)模式,當(dāng)我的學(xué)完再回頭看這些問題,就很自然的迎刃而解了。
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
Spring Data JPA的Audit功能審計(jì)數(shù)據(jù)庫的變更
數(shù)據(jù)庫審計(jì)是指當(dāng)數(shù)據(jù)庫有記錄變更時(shí),可以記錄數(shù)據(jù)庫的變更時(shí)間和變更人等,這樣以后出問題回溯問責(zé)也比較方便,本文討論Spring Data JPA審計(jì)數(shù)據(jù)庫變更問題,感興趣的朋友一起看看吧2021-06-06Java中java.sql.SQLException異常的正確解決方法(親測有效!)
SQLException是在Java中處理數(shù)據(jù)庫操作過程中可能發(fā)生的異常,通常是由于底層數(shù)據(jù)庫操作錯(cuò)誤或違反了數(shù)據(jù)庫規(guī)則而引起的,下面這篇文章主要給大家介紹了關(guān)于Java中java.sql.SQLException異常的正確解決方法,需要的朋友可以參考下2024-01-01SpringBoot異步調(diào)用方法實(shí)現(xiàn)場景代碼實(shí)例
這篇文章主要介紹了SpringBoot異步調(diào)用方法實(shí)現(xiàn)場景代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04spring boot+mybatis 多數(shù)據(jù)源切換(實(shí)例講解)
下面小編就為大家?guī)硪黄猻pring boot+mybatis 多數(shù)據(jù)源切換(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09Spring?Cloud?Gateway動態(tài)路由Apollo實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Spring?Cloud?Gateway動態(tài)路由通過Apollo實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10