命令模式&中介者模式

25,433 阅读9分钟

有情怀,有干货,微信搜索【三太子敖丙】关注这个有一点点东西的程序员。

本文 GitHub github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

最近在跟大家分享设计模式系列的文章有学妹问我,命令模式、策略模式、工厂模式 它们分别有啥区别?看代码的实现上感觉没啥区别呀?

之前已经跟大家分享了策略模式以及工厂模式感兴趣的同学可以再去复习一下,今天我们就先重点分析一下命令模式然后再来看看它们的区别是啥?

往期回顾:

命令模式

定义
  • 提供一个统一的方法来封装命令,通过参数条件来判断选择执行什么命令动作。
  • 允许将每一个命令存储在一个队列中。

整体结构图如下:

结构图中重要角色解释:

  • 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实例。来提高代码的复用性以及扩展性。

中介模式:整体来说这个更加不怎么应用,虽然能起到对象的解耦合,但是也有副作用,而且在我们的真实业务场景中也很少会遇到这样的场景,了解一下实现原理即可,至于与观察者的区别,上面也有讲到,更多我们可能是已经在使用一些中间件消息队列去处理了。

我是敖丙,你知道的越多,你不知道的越多,感谢各位人才的:点赞收藏评论,我们下期见!


文章持续更新,可以微信搜一搜「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。