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