命令模式之中介者模式
最近在跟大家分享設(shè)計模式系列的文章有學(xué)妹問我,命令模式、策略模式、工廠模式 它們分別有啥區(qū)別?看代碼的實現(xiàn)上感覺沒啥區(qū)別呀?
我說:文章可能有點長,你忍一下
之前已經(jīng)跟大家分享了策略模式以及工廠模式感興趣的同學(xué)可以再去復(fù)習(xí)一下,今天我們就先重點分析一下命令模式然后再來看看它們的區(qū)別是啥?
命令模式
定義
- 提供一個統(tǒng)一的方法來封裝命令,通過參數(shù)條件來判斷選擇執(zhí)行什么命令動作。
- 允許將每一個命令存儲在一個隊列中。
整體結(jié)構(gòu)圖如下:
結(jié)構(gòu)圖中重要角色解釋:
- Command(命令類):定義命令的抽象封裝類。
- ConcreteCommand(具體命令類):對Command類進行實現(xiàn),說白了就是具體的命令的實際實現(xiàn)類。
- Receiver(接收者):執(zhí)行命令關(guān)聯(lián)的操作類。
- Invoker(調(diào)用者):觸發(fā)命令類,即外部操作事件觸發(fā)執(zhí)行。
- Client(客戶端):實例化具體命令對象,及接收者的實際類。
整個結(jié)構(gòu)其實看上去還是比較難理解的,但是既然開始在學(xué)設(shè)計模式了,那肯定每種設(shè)計模式都要有了解,來提升自己的知識面
為了加深理解,我還是舉一個好理解的例子:
大家對中國古代君主制度肯定很熟悉?;实劭梢葬槍κ值紫路痰墓屗齻兛梢允杖』蛘甙l(fā)放奏折。那其實這里面我個人感覺就可以體現(xiàn)命令模式。
公公 相當于命令模式的接受者(Receiver),執(zhí)行皇帝的命令,收取早朝奏折(ConcreteCommand) 還是頒布圣旨(ConcreteCommand)
皇帝 相當于命令模式的調(diào)用者(Invoker)
老規(guī)矩,例子說完,看看代碼吧!
- // 定義 命令類
- public interface Command {
- // 執(zhí)行的方法
- void execute();
- }
- // 定義接收者-公公的角色
- public class Receiver {
- public void Charge(){
- System.out.println("收取奏折");
- }
- public void Issue(){
- System.out.println("頒布圣旨");
- }
- }
- //具體命令類one,收取奏折命令
- public class ConcreteCommandOne implements Command {
- // 接受者,這里可以理解為公公
- private Receiver receiver;
- public ConcreteCommandOne(Receiver receiver) {
- this.receiver = receiver;
- }
- @Override
- public void execute() {
- // 收取奏折
- receiver.Charge();
- }
- }
- // 具體命令類two,頒布圣旨
- public class ConcreteCommandTwo implements Command {
- // 接受者,這里可以理解為公公
- private Receiver receiver;
- public ConcreteCommandTwo(Receiver receiver) {
- this.receiver = receiver;
- }
- @Override
- public void execute() {
- // 頒布圣旨
- receiver.Issue();
- }
- }
- // 調(diào)用者,皇帝
- public class Invoker {
- private Command command;
- public Invoker(Command command) {
- this.command = command;
- }
- // 本次需要執(zhí)行的命令
- public void action() {
- command.execute();
- }
- }
- // 測試demo
- public static void main(String[] args) {
- // 實例化一個公公 接收者
- Receiver receiver =new Receiver();
- // 公公 當前能有接收到的幾種命令
- Command commandOne = new ConcreteCommandOne(receiver);
- Command commandTwo = new ConcreteCommandTwo(receiver);
- // 皇帝 發(fā)號命令 觸發(fā)執(zhí)行方法
- Invoker invoker =new Invoker(commandOne);
- invoker.action();
- // result: 收取奏折
- Invoker invokerTwo =new Invoker(commandTwo);
- invokerTwo.action();
- // result:頒布圣旨
- }
以上就是簡單的代碼實現(xiàn)了,通過Invoker(皇帝)的選擇可以讓Receiver(公公)確定去執(zhí)行什么命令。這其實就是命令模式的一種簡單體現(xiàn)。
細心的同學(xué)不知道有沒有發(fā)現(xiàn)一個問題,在定義里面
- 允許將每一個命令存儲在一個隊列中。
我們這里是沒有體現(xiàn)隊列的,其實這個實現(xiàn)也很簡單。在main方法中添加一個隊列就可以了。
- public static void main(String[] args) {
- // 實例化一個公公 接收者
- Receiver receiver = new Receiver();
- // 公公 當前能有接收到的幾種命令
- Command commandOne = new ConcreteCommandOne(receiver);
- Command commandTwo = new ConcreteCommandTwo(receiver);
- // 存儲命令
- Queue<Command> queue = new LinkedList<>();
- queue.add(commandOne);
- queue.add(commandTwo);
- // 批量執(zhí)行
- for (Command command : queue) {
- Invoker invoker = new Invoker(command);
- invoker.action();
- }
- }
這里我想給大家做一個擴展點,這也是我之前看到過一種校驗寫法。
大家在真實的工作中肯定會遇到很多一些接口的校驗,怎么去寫這個校驗邏輯,怎么做到代碼的復(fù)用、抽象等這其實是一個比較難的問題!
還是大致的來看下結(jié)構(gòu)圖吧!!!
demo代碼,我也給大家寫出來,需要注意的是我們需要實現(xiàn) ApplicationContextAware 里面的afterPropertiesSet 方法。
- // 定義抽象校驗方法
- public abstract class ValidatePlugin {
- public abstract void validate();
- }
- // 抽象規(guī)則執(zhí)行器
- public abstract class ValidatePluginExecute {
- protected abstract List<ValidatePlugin> getValidatePlugins();
- public void execute() {
- final List<ValidatePlugin> validatePlugins = getValidatePlugins();
- if (CollectionUtils.isEmpty(validatePlugins)) {
- return;
- }
- for (ValidatePlugin validatePlugin : validatePlugins) {
- // 執(zhí)行校驗邏輯,這里大家可以根據(jù)自己的實際業(yè)務(wù)場景改造
- validatePlugin.validate();
- }
- }
- }
- // 具體測試規(guī)則
- @Component("validatePluginOne")
- public class ValidatePluginOne extends ValidatePlugin {
- @Override
- public void validate() {
- System.out.println("validatePluginOne 規(guī)則校驗");
- }
- }
- // 具體執(zhí)行器,把需要執(zhí)行的規(guī)則添加到 validatePlugins 中
- @Component("testValidatePlugin")
- public class TestValidatePlugin extends ValidatePluginExecute implements ApplicationContextAware, InitializingBean {
- protected ApplicationContext applicationContext;
- private List<ValidatePlugin> validatePlugins;
- @Override
- public void afterPropertiesSet() {
- // 添加規(guī)則
- validatePlugins = Lists.newArrayList();
- validatePlugins.add((ValidatePlugin) this.applicationContext.getBean("validatePluginOne"));
- }
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
- @Override
- protected List<ValidatePlugin> getValidatePlugins() {
- return this.validatePlugins;
- }
- }
- // 測試demo
- public static void main(String[] args) {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- TestValidatePlugin testValidatePlugin = (TestValidatePlugin) applicationContext.getBean("testValidatePlugin");
- testValidatePlugin.execute();
- }
這個只是一個簡單的測試demo,為了讓大家有一個思考,設(shè)計模式不一定是照搬代碼。更多是開拓自己的視野,提升自己解決問題的能力。
針對不同的一些接口,我們只需要在TestValidatePlugin 中添加具體校驗規(guī)則就可以了,整體的擴展性就變高了,看上去也比較高大上。
所以上面提到的命令模式、策略模式、工廠模式區(qū)別是什么呢?
- 命令模式:屬于行為型設(shè)計模式,在命令模式中,不同的命令執(zhí)行過程中會產(chǎn)生不同的目的結(jié)果,而且不同的命令是不能替換的。
- 策略模式 :屬于行為型設(shè)計模式,在策略模式中,重點在于針對每一種策略執(zhí)行,解決根據(jù)運行時狀態(tài)從一組策略中選擇不同策略的問題
- 工廠模式:屬于創(chuàng)建型設(shè)計模式,在工廠模式中,重點在于封裝對象的創(chuàng)建過程,這里的對象沒有任何業(yè)務(wù)場景的限定,可以是策略,但也可以是其他東西
所以針對設(shè)計模式,其實我理解的還是只說明了一個問題,不同的設(shè)計模式都是為了針對處理不同的場景,不同業(yè)務(wù)場景有不同的寫法。
中介者模式
中介者模式,看這個名字也能理解出來,定一個中間結(jié)構(gòu)來方便管理下游組織。
那么什么是中介模式呢?
- 在GoF 中的《設(shè)計模式》中解釋為:中介模式定義了一個單獨的(中介)對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互。
再來看看這個結(jié)構(gòu)圖吧:
- Mediator(抽象中介者):用來定義參與者與中介者之間的交互方式
- ConcreteMediator(具體中介者):實現(xiàn)中介者定義的操作,即就是實現(xiàn)交互方式。
- Colleague(抽象同事角色):抽象類或者接口,主要用來定義參與者如何進行交互。
- ConcreteColleague(具有同事角色):很簡單,就是具體的實現(xiàn)Colleague中的方法。
以上結(jié)構(gòu)定義來自設(shè)計模式之美
看這個結(jié)構(gòu)圖理解出來,其實是跟之前為大家寫的一篇觀察者模式有點相同的,感興趣的同學(xué)可以再去復(fù)習(xí)一下。
老規(guī)矩,還是具體舉例代碼實現(xiàn)一下
高鐵系統(tǒng)大家應(yīng)該清楚有一個調(diào)度中心,用來控制每一輛高鐵的進站順序,如果沒有這個調(diào)度中心,當同時有三量高鐵都即將進站時,那他們就需要兩兩相護溝通。
假設(shè)有其中的一輛動車沒有溝通到,那就將發(fā)生不可估量的錯誤,所以就需要通過這個調(diào)度中心來處理這個通信邏輯,同時來管理當前有多少車輛等待進站等。
- // 抽象參與者, 也可以使用abstract 寫法
- public interface Colleague {
- // 溝通消息
- void message();
- }
- // 抽象中介者
- public interface Mediator {
- // 定義處理邏輯
- void doEvent(Colleague colleague);
- }
- // 具體參與者
- @Component
- public class MotorCarOneColleague implements Colleague {
- @Override
- public void message() {
- // 模擬處理業(yè)務(wù)邏輯
- System.out.println("高鐵一號收到消息!?。?quot;);
- }
- }
- @Component
- public class MotorCarTwoColleague implements Colleague {
- @Override
- public void message() {
- System.out.println("高鐵二號收到消息?。。?quot;);
- }
- }
- @Component
- public class MotorCarThreeColleague implements Colleague {
- @Override
- public void message() {
- System.out.println("高鐵三號收到消息?。?!");
- }
- }
- // 具體中介者
- @Component
- public class DispatchCenter implements Mediator {
- // 管理有哪些參與者
- @Autowired
- private List<Colleague> colleagues;
- @Override
- public void doEvent(Colleague colleague) {
- for(Colleague colleague1 :colleagues){
- if(colleague1==colleague){
- // 如果是本身高鐵信息,可以處理其他的業(yè)務(wù)邏輯
- // doSomeThing();
- continue;
- }
- // 通知其他參與
- colleague1.message();
- }
- }
- }
- // 測試demo
- public static void main(String[] args) {
- // 初始化spring容器
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- // 獲取中介者,調(diào)度中心
- DispatchCenter dispatchCenter = (DispatchCenter) applicationContext.getBean("dispatchCenter");
- // 一號高鐵 發(fā)送消息出去
- MotorCarOneColleague motorCarOneColleague = (MotorCarOneColleague) applicationContext.getBean("motorCarOneColleague");
- // 通過調(diào)度中心溝通信息
- dispatchCenter.doEvent(motorCarOneColleague);
- // result:高鐵三號收到消息?。?!
- // 高鐵二號收到消息!?。?nbsp;
- // 二號高鐵 發(fā)送消息出去
- MotorCarTwoColleague motorCarTwoColleague = (MotorCarTwoColleague)applicationContext.getBean("motorCarTwoColleague");
- dispatchCenter.doEvent(motorCarTwoColleague);
- // result:高鐵一號收到消息?。?!
- // 高鐵三號收到消息!??!
- }
中介者模式demo代碼就算完成了,通過這個demo大家應(yīng)該能發(fā)現(xiàn),中介者還是很好理解的。
但是中介者的應(yīng)用場景還是比較少見的,針對一些類依賴嚴重,形成的類似網(wǎng)狀結(jié)構(gòu),改成一個類似與蒲公英一樣結(jié)構(gòu),由中間向外擴散,來達到解耦合的效果。
更多在一個UI界面控件里面比較常見,當然在Java里面java.util.Timer 也可以理解為中介者模式,因為它能控制內(nèi)部線程如何去運行比如多久運行一次等。
上面提到中介者和觀察者模式很像,通過demo代碼大家也能發(fā)現(xiàn)這一點
觀察者模式中觀察者和被觀察者我們基本時固定的,而中介者模式中,觀察者和被觀察者時不固定的,而且中介者可能會最后變成一個龐大的原始類。
總結(jié)
命令模式:雖然不怎么常見,但是我們還是要區(qū)分它與工廠模式以及策略模式的區(qū)別是啥,應(yīng)用場景是啥,能給我們帶來什么思考。
比如我最后的那個例子,命令模式可以實現(xiàn)命令的存儲,本質(zhì)是將命令維護在一個隊列中,那么在我們的業(yè)務(wù)代碼中 我們?yōu)槭裁床荒芤餐ㄟ^一個數(shù)組來維護一些接口校驗依賴,里面存放需要校驗的bean實例。來提高代碼的復(fù)用性以及擴展性。
中介模式:整體來說這個更加不怎么應(yīng)用,雖然能起到對象的解耦合,但是也有副作用,而且在我們的真實業(yè)務(wù)場景中也很少會遇到這樣的場景,了解一下實現(xiàn)原理即可,至于與觀察者的區(qū)別,上面也有講到,更多我們可能是已經(jīng)在使用一些中間件消息隊列去處理了。
我是敖丙,你知道的越多,你不知道的越多,我們下期見!