Bug辉的博客 不忘初心,方得始终!

GoF设计模式 - 调停者模式

2017-08-08
Bug辉 [原创文章]

本文为博主原创文章,请遵守文章最后的版权申明。


调停者模式(Mediator),行为型模式。在一个复杂系统中,对象与对象间很容易形成一个网状的依赖关系。这种系统的各个模块间耦合的过于紧密,容易导致牵一发而动全身的结果。调停者模式通过引入一个中间对象来将系统结构从网状结构变成星形结构,以此解除对象之间的复杂的相互依赖。

《GoF设计模式 - 概述》一文中已经讲述了设计原则等基础知识,如果还没有看,请先看完这篇文章

调停者模式

调停者模式

角色以及职责

  1. Mediator: 调停者的抽象。系统中各个组件间通讯的“中介”类。
  2. ConcreteMediator: Mediator 的实现类。
  3. Colleague: 同事类,系统中各个相互协作的组件的抽象。
  4. ConcreteColleague: Colleague 的实现类。

适用场景

当系统内类(模块)数量比较多且各个类间相互依赖比较复杂(呈现出网状的依赖关系)时,希望系统的某些部分发生变化时尽可能少的影响到其他的部分。简单的说就是想使各个模块尽可能的保持独立,使整个系统的各个模块松散的耦合。这种场景适合使用调停者模式!不过,前提是要抽象的合理!(ーー゛)

实例

在SOA架构的web应用以及安卓等领域常常会用到消息总线、事件总线等机制。仔细去观察其实会发现这些其实都是调停者模式的实践。本文将通过一个事件总线的例子来演示调停者模式。

在此之前,我想吐槽一句。调停者模式的实例实在是比较难想,因为我平时工作中确实比较少用到这个模式,网络上尽是一些“香蕉”、“苹果”之类的无意义的比喻,以至于上周跳票了一篇文章。我想了很久才想起来这个例子。。

本文为了简化代码,所以并没有对事件本身进行封装,而是直接采用了Object作为消息。关于这个问题,相信聪明的同学应该知道这里是可以改进的。

如果路过的同学认为这里有什么不对的,欢迎指正!

类图

调停者模式

编码实现

首先定义好事件总线的抽象,也就是Mediator

/**
 * 事件总线
 * @author: Elvin Zeng
 * @date: 17-8-8.
 */
public interface EventBus {
    /**
     * 发布事件
     * @param type 事件类型
     * @param message 消息对象
     */
    void post(String type, Object message);
}

再来定义一个事件处理器抽象类,这个类对应着调停者模式中的ColleagueEventHandler在这里也可以定义为一个接口,这个可以根据需要自由发挥。

/**
 * 事件处理器
 * @author: Elvin Zeng
 * @date: 17-8-8
 */
public abstract class EventHandler {
    private EventBus eventBus;

    public EventHandler(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public EventBus getEventBus() {
        return eventBus;
    }

    /**
     * 处理事件
     * @param message 消息对象
     */
    public abstract void handle(Object message);
}

然后实现一个事件总线的默认实现类,对应着调停者模式中的ConcreteMediator。 这里将DefaultEventBus实现为了单例类

/**
 * 消息总线默认实现
 * @author: Elvin Zeng
 * @date: 17-8-8
 */
public class DefaultEventBus implements EventBus {
    private static DefaultEventBus instance;
    private static Object lockObj = new Object();
    private Map<String, List<EventHandler>> eventHandlerMap;

    private DefaultEventBus(){
        eventHandlerMap = new HashMap<>();
    }

    public static DefaultEventBus getInstance(){
        if (null == instance){
            synchronized (lockObj){
                if (null == instance){
                    instance = new DefaultEventBus();
                }
            }
        }
        return instance;
    }


    @Override
    public void post(String type, Object message) {
        List<EventHandler> handlers = eventHandlerMap.get(type);
        if (null != handlers){
            for (EventHandler handler : handlers){
                handler.handle(message);
            }
        }
    }


    /**
     * 注册事件处理器
     * @param type 感兴趣的事件类型
     * @param eventHandler 事件处理器
     */
    public synchronized void registerHandler(String type, EventHandler eventHandler) {
        if (eventHandlerMap.containsKey(type)){
            List<EventHandler> handlers = eventHandlerMap.get(type);
            handlers.add(eventHandler);
        }else{
            List<EventHandler> handlers = new ArrayList<>();
            handlers.add(eventHandler);
            eventHandlerMap.put(type, handlers);
        }
    }
}

接着实现三个事件处理器的实现类,对应着调停者模式中的ConcreteColleague

/**
 * 键盘映射管理器
 * @author: Elvin Zeng
 * @date: 17-8-8
 */
public class KeyMapManager extends EventHandler {

    public KeyMapManager(EventBus eventBus) {
        super(eventBus);
    }

    @Override
    public void handle(Object message) {
        String key = message.toString();
        System.out.println("你按下了:" + key);
        if ("alt+d".equals(key)){
            getEventBus().post("main_form_event", "hideWindow");
        }else if ("alt+s".equals(key)){
            getEventBus().post("main_form_event", "showWindow");
        }
    }
}
/**
 * 程序主窗口
 * @author: Elvin Zeng
 * @date: 17-8-8
 */
public class MainForm extends EventHandler{

    public MainForm(EventBus eventBus) {
        super(eventBus);
    }

    @Override
    public void handle(Object message) {
        String command = message.toString();
        if ("showWindow".equals(command)){
            show();
            getEventBus().post("audio_event", "show-window.mp3");
        }else if ("hideWindow".equals(command)){
            hide();
            getEventBus().post("audio_event", "hide-window.mp3");
        }else{
            System.out.println("未知事件");
        }
    }

    public void show(){
        System.out.println("显示窗口");
    }

    public void hide(){
        System.out.println("隐藏窗口");
    }
}
/**
 * 音效播放器
 * @author: Elvin Zeng
 * @date: 17-8-8
 */
public class AudioPlayer extends EventHandler{

    public AudioPlayer(EventBus eventBus) {
        super(eventBus);
    }

    @Override
    public void handle(Object message) {
        System.out.println("播放音效[" + message.toString() + "]");
    }
}

最后是客户端

/**
 * Client
 * @author: Elvin Zeng
 * @date: 17-8-8.
 */
public class App {
    public static void main(String[] args) {
        DefaultEventBus eventBus = DefaultEventBus.getInstance();

        //  tips: type常量在程序中可以抽取到常量类中
        eventBus.registerHandler("keyboard_event", new KeyMapManager(eventBus));
        eventBus.registerHandler("audio_event", new AudioPlayer(eventBus));
        eventBus.registerHandler("main_form_event", new MainForm(eventBus));

        //  老板来了,赶紧隐藏窗口!!
        eventBus.post("keyboard_event", "alt+d");
        //  老板走了,显示窗口继续玩。
        eventBus.post("keyboard_event", "alt+s");

    }

}

上面的代码仅仅只是模拟的交互,实际程序中,可以通过主窗口的事件监听器去发送对应的消息。

执行结果

$ java -jar mediator-1.0-SNAPSHOT.jar          
你按下了:alt+d
隐藏窗口
播放音效[hide-window.mp3]
你按下了:alt+s
显示窗口
播放音效[show-window.mp3]

调停者模式分析

当系统中的模块越来越多之后,很容易形成一个网状的依赖关系。这种情况下,所有模块都是各自独立的与其他模块直接通信的。 结果就是整个系统的各个模块间耦合的非常紧密,以至于出现牵一发而动全身的结果。就像下面这张图。
网状结构

迪米特法则要求我们尽可能的“不和陌生人说话”,根据这一理念,调停者模式通过引入一个“中介”将这个系统变成了一个松耦合的系统。就像下面这张图。 星形结构
我们把这个“中介”叫调停者,所有的模块都只和调停者通信,而不用知道系统的其他部分。

注:由于上面两张图传播的太广泛了,实在不知道出处在哪里,所以没有办法注明原始出处。以上两张图片来自网络,不是我画的,特此申明!

关于调停者模式,前辈们还给出了一个更加形象的比喻:调停者就像是主板,而各个组件就是CPU、内存、显卡、硬盘等设备。所有的设备通过主板来与其他组件通信,组件之间并不需要建立什么直接的联系。这个比喻是相当形象的!

对于本例中这种问题,其实我们也可以直接通过观察者模式来解决——直接在事件监听器中响应相应的事件。在系统模块不多的时候,这样做其实是更直接明了的。不过,当系统规模变大以后,各个模块的事件监听器中直接引用其他模块的代码会导致各模块紧耦合。也就是说,会出现上面说的那种网状结构的系统。本例中,引入调停者之后,所有的模块不再直接相互调用。取而代之的是所有的模块都直接与调停者(本文中的事件总线)通信,这使得所有的模块都不用直接去调用其他模块。

调停者模式其实原理上并没有多难,只是使用起来着实不易。原因有很多,既有模式本身的问题,也有程序员的问题。比如:

  • 对于每天按照成熟流程写代码的普通Web开发者来说,比较少接触到这种场景。
  • 系统中各部分的交互往往并不是发布事件这么简单,如果出现更复杂的交互,可能会导致调停者自身的逻辑非常臃肿,逻辑复杂。
  • 很多时候其实用调停者模式也没啥必要,因为系统中的对象并没有那么复杂的网状依赖关系。如果刻意去使用这个模式的话,容易滥用。

文章的最后,我想问一句路过的同学,你实际工作中还在其他什么地方用过或者见过调停者模式吗?如果有,请告诉我!我们一起成长。

参考


版权声明

知识共享许可协议
若无特殊说明则文章内容均为博主原创内容,包括但不限于代码、文字、图片。
《GoF设计模式 - 调停者模式》 Bug辉 采用 知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议 进行许可。
转载文章时文章署名请注明原作者为Bug辉(Elvin Zeng、zenghui、曾辉也行)并带上原文链接:https://www.bughui.com/2017/08/08/gof-design-pattern-mediator/

鉴于目前国人版权意识比较薄弱,所以路过的同学可能并不了解CC协议。在此特地介绍一下!上述协议大概的意思是(这里只是简单解释,并不替代协议,以上述协议为准):
  • 权利:遵守协议的情况下你可以在任何媒介以任何形式复制、发行本作品。
  • 约束:使用本作品得保留署名(作者+原文链接),不得声称或暗示文章是你创作的。
  • 约束:你不可以将本作品用于商业目的。
  • 约束:如果你再混合、转换、或者基于本作品创作,你不可以发布修改后的作品。
  • 在得到作者允许的情况下你可以不用受上述条款约束。
不论本作品是否对你有益,不论你是否认同本作品的观点,本作品都是作者的劳动产物。尊重别人的劳动别人才会尊重你的劳动是吧!


类似文章

下一篇 换工作ing

评论

打赏时请在备注信息中填上你的称呼!好让我把你的名字加入致谢名单