设计模式——2_4 中介者(Mediator)

我寄愁心与明月,随风直到夜郎西

——李白《闻王昌龄左迁龙标遥有此寄》

文章目录

  • 定义
  • 图纸
  • 一个例子:怎么调度一组地铁
    • 站台和地铁
    • 开车
    • 指挥中心
  • 碎碎念
    • 中介者和表单
    • 平台思想
      • 但是这种平台便利性是要付出代价的
        • 变化隔离原则
    • 姑妄言之

定义

用一个中介者对象来封装一系列的对象交互。中介者使各个对象之间不需要显式的相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互




图纸

在这里插入图片描述




一个例子:怎么调度一组地铁

地铁,就是那种在地底下(也未必,深圳的11号线就能看海,听说重庆的地铁还能过楼?)沿着轨道跑的列车

一般来说一个车站会有两个不同方向的站台,而同一个方向的站台在同一时间显然只能有一部列车可以同时出现

那么当一列地铁即将到达某一个站台的时候,是需要确认目标站台上有没有地铁正在停靠的

当你把上述行为抽象成代码的时候,中介者可以帮助你优雅的实现对地铁进行调度的过程,而这正是我们这次的例子:



站台和地铁

无论如何,站台和地铁都一定有对应自己的类,就像这样:

在这里插入图片描述

/**
 * 站台
 */
public class Platform {

    /**
     * 站台名称
     */
    private String name;
    /**
     * 当前停靠的地铁
     */
    private Subway subway;

    public Platform(String name) {
        this.name = name;
    }

    /**
     * 进站
     */
    public synchronized void in(Subway subway) {
        System.out.printf("%s 即将进入 %s%n", subway.getCode(), name);
        this.subway = subway;
        System.out.printf("%s 进入了 %s%n", subway.getCode(), name);
    }

    /**
     * 进站
     */
    public synchronized void out() {
        System.out.printf("%s 离开 %s%n", subway.getCode(), name);
        this.subway = null;
    }

    /**
     * 是否是空站
     */
    public synchronized boolean isEmpty() {
        return subway == null;
    }
}

/**
 * 地铁
 */
public class Subway {

    private String code;

    public Subway(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

我们新建了 Platform(站台)Subway(地铁) 两个类分别用于表示站台和地铁,站台上可以停靠地铁,而且通过 isEmpty 方法可以告诉 client 当前这个站台是否停靠了地铁


接着问题来了,client代码 要怎么指挥 Subway(地铁) 往前走呢?



开车

打个比方,现在我们有 站台A/B/C,有电灯号和灯笼号两部地铁,同时规定:

  1. 两部地铁都是沿着A->B->C这个方向往前移动

  2. 电灯号从站台A出发,灯笼号从站台B出发

首先我们要初始化他,就像这样:

Platform a = new Platform("A");
Platform b = new Platform("B");
Platform c = new Platform("C");

Subway subway_1 = new Subway("电灯号");
Subway subway_2 = new Subway("灯笼号");

接着我们让电灯号进入A站台,再让灯笼号进入A站台;这时候因为电灯号还在站台里,所以程序应该提示我 不能进入。接着让灯笼号离开A站台,再让电灯号进入,就像这样:

private static Map<Subway, Integer> subwayMap = new HashMap<>();//用于记录地铁的位置
private static Platform[] ps;

public static void main(String[] args) {
	//初始化
        ps = new Platform[]{new Platform("A"), new Platform("B"), new Platform("C")};
        Subway subway_1 = new Subway("电灯号");
        Subway subway_2 = new Subway("灯笼号");

        subwayMap.put(subway_1, 0);
        ps[0].in(subway_1);
        subwayMap.put(subway_2, 1);
        ps[1].in(subway_2);

        move(subway_1);//电灯号往前走,被挡住
        move(subway_2);//灯笼号往前走
        move(subway_1);//电灯号往前走
}

public static void move(Subway subway) {
        Integer position = subwayMap.get(subway);
        int nextPosition = position + 1 < ps.length ? position + 1 : 0;

        if (ps[nextPosition].isEmpty()) {
            //空站台可以驶入
            ps[position].out();//驶出
            ps[nextPosition].in(subway);//驶入
            subwayMap.put(subway, nextPosition);
        } else {
            System.out.println("还有车,无法驶入");
        }
}

在这里插入图片描述

这段代码有两个问题:

  1. 里面出现了可以抽离出来的部分,也就是move方法

  2. 我们向 client 暴露了站台的内部结构,在实战中,你一定不希望这种事的发生


事实上这两个问题都可以通过创建一个平台来解决。于是乎,为了解决这样的问题,我们引入了 指挥中心 的概念


指挥中心

就像这样:

/**
 * 指挥中心
 */
public class ControlCenter {

    private Map<Subway, Integer> subwayMap = new HashMap<>();//用于记录地铁的位置
    private Platform[] ps = new Platform[]{new Platform("A"), new Platform("B"), new Platform("C")};

    /**
     * 推动某部地铁往前走
     */
    public void move(Subway subway) {
        Integer position = subwayMap.get(subway);
        int nextPosition = position + 1 < ps.length ? position + 1 : 0;

        if (ps[nextPosition].isEmpty()) {
            //空站台可以驶入
            ps[position].out();//驶出
            ps[nextPosition].in(subway);//驶入
            subwayMap.put(subway, nextPosition);
        } else {
            System.out.println("还有车,无法驶入");
        }
    }

    public void addSubway(Subway subway, int position) {
        subwayMap.put(subway, position);
        ps[position].in(subway);
    }
}
 public static void main(String[] args) {
        ControlCenter controlCenter = new ControlCenter();

        Subway s1 = new Subway("电灯号");
        Subway s2 = new Subway("灯笼号");

        controlCenter.addSubway(s1,0);
        controlCenter.addSubway(s2,1);

        controlCenter.move(s1);//电灯号往前走,被挡住
        controlCenter.move(s2);//灯笼号往前走
        controlCenter.move(s1);//电灯号往前走
    }

我们把上面所说的内容抽象到了 ControlCenter(控制中心) 中,让 Subway 对于 Platform 有关的变动不要自己去操作,而是让 ControlCenter 代劳,从而实现对内容和关系的隐藏,以及集中化管理

而这正是一个标准的中介者实现


可能这个例子过于简单,没能把中介者的威力完全体现。事实上在实际开发中,当你的某个局部内的各个组件之间关联非常密切的时候,中介者的存在是不可或缺的。他让你可以从上层俯瞰所有组件之间的结构,而不是在各个组件中去找某个动作实现后会对谁造成影响




碎碎念

中介者和表单

表单,应该是程序设计历史上第一种人机交互方式,也是最常用的交互形式

而表单中的内容通常会有很多级联操作,比如说:

  • 密码框和重复密码框,如果两者输入不一致,我应该提示用户吧
  • 级联下拉框,选择第一级后,后面的下拉框里的内容需要被修改吧
  • 点击重置按钮,已经填的所有信息都应该被清空吧

问题在于,类似这些 在一个控件中,对另一个或几个控件进行操作的业务代码,应该写到哪里去呢?

第一个思路 就是让对象间自己进行交互,那显然不现实。这就意味着一个 重置 按钮对象 必须要获得当前表单内所有控件的引用,那我还怎么复用他?他的逻辑会变得很复杂,因为不同的控件会有不同的重置方式,甚至相同控件在不同的状态下也有不同的重置方式

更优解 其实就是中介者,而且这个中介者很好找,表单自身对象就可以来做这个中介者。可以让表单内的所有对象都来和这个表单对象进行交互,比如说:

  • 密码框和重复密码框输入完后发送信息给表单对象通知他验证
  • 选择第一级级联下拉框后通知表单对象变化下一级级联下拉框
  • 点击重置按钮后,通知表单对象重置表单数据

至此,表单内主体变化对象和被驱动变化的表单对象之间的耦合被解除了,因为只有表单对象需要知道每个操作到底涉及到了多少控件



平台思想

几乎所有的设计模式出现的初衷都是为了降低对象之间的耦合。我们一直讲代码要高内聚、低耦合,高耦合就意味着难以维护,好像一切都是耦合的罪过。既然如此,那我们不禁要问:

耦合可以被消灭吗?


答案是否定的,因为一定程度的耦合是必须的。对象是不可能完全独立、不依赖任何其他对象的。一点耦合都没有的代码,什么事情都完成不了


可是在实践中我们发现,具体对象之间的关联会让我们的系统结构变得复杂(如果画图的话,画出来的效果就像是一个纵横交错的网)

在我们维护系统的时候,尝试勾连出这样子的网的时候,这会让我们死很多脑细胞

所以作为一个热爱生命的人,我们引入了平台思想,让N个相互之间存在关联的对象,尽可能都和同一个对象打交道,然后在这个平台里集中处理一些关联信息,亦或是分发信息


这种设计思路非常非常非常的常见,无论是之前文章里出现过的 工厂方法(Factory Method)、抑或是外观(Facade),又或者是之后会出现的访问者(Visitor) 都涉及到了这种思想,同时这种思想还是IOC框架实现的基础


但是这种平台便利性是要付出代价的

随着系统的扩大,这个负责对象交互的平台一定会愈发复杂,而这部分 复杂 其实就是原本各自对象之间要进行的交互。也就是说,使用平台并不是彻底消灭了 复杂,而是把他们集中起来处理

这是符合设计原则的,因为其中有一条是这样写的:

变化隔离原则

找出应用中可能需要变化的地方,把他们独立出来,不要和那些不需要变化的代码混合在一起

依据这个原则,所以我们把对象之间的交互和对象自身要处理的业务进行隔离。因为对象之间的交互总是充满不确定性的,而对象自身的业务通常是在编码时就已经确定的



姑妄言之

说白了,中介者其实就是一个跟所有人都有关联的对象。那其实我们的古人早就提到过中介者这样的概念,那时的中介者,通常是我们头顶的月亮。李白就写过:我寄愁心与明月,随风直到夜郎西 这样的诗句,其实就是让大家都共享的月亮帮他传递信息嘛。

所以哪怕人真的是孤岛,又怎么可能真的那么孤单。只要仰望夜空,就一定有人此时此刻和你一起在同一片星空下仰望同一个月亮




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/440937.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

吴恩达机器学习-可选实验室:特征工程和多项式回归(Feature Engineering and Polynomial Regression)

文章目录 目标工具特征工程和多项式回归概述多项式特征选择功能备用视图扩展功能复杂的功能 恭喜! 目标 在本实验中&#xff0c;你将:探索特征工程和多项式回归&#xff0c;它们允许您使用线性回归的机制来拟合非常复杂&#xff0c;甚至非常非线性的函数。 工具 您将利用在以…

PageHelper 又给我上了一课!

多年不用PageHelper了&#xff0c;最近新入职的公司&#xff0c;采用了此工具集成的框架&#xff0c;作为一个独立紧急项目开发的基础。项目开发起来&#xff0c;还是手到擒来的&#xff0c;但是没想到&#xff0c;最终测试的时候&#xff0c;深深的给我上了一课。 # 我的项目发…

SpringCloud-SpringBoot读取Nacos上的配置文件

在 Spring Boot 应用程序中&#xff0c;可以使用 Spring Cloud Nacos 来实现从 Nacos 服务注册中心和配置中心读取配置信息。以下是如何在 Spring Boot 中读取 Nacos 上的配置文件的步骤&#xff1a; 1. 引入依赖 首先&#xff0c;在 Spring Boot 项目的 pom.xml 文件中添加 …

2007-2022年上市公司迪博内部控制评价缺陷数量数据

2007-2022年上市公司迪博内部控制评价缺陷数量数据 1、时间&#xff1a;2007-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、是否存在财报内控重大缺陷、财报内控重大缺陷数量、是否存在财报内控重要缺陷、财报内…

亚信安慧AntDB:“融合+实时”引领数据库创新

在当今多变的数据应用场景中&#xff0c;AntDB作为行业领先的超融合流式实时数仓&#xff0c;秉承着“融合实时”的研发理念&#xff0c;全面应对企业日益复杂的数据处理需求。通过SQL接口访问多种执行引擎&#xff0c;AntDB在实现交易、分析等多重能力的“超融合”方面取得了显…

智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】

目录 一&#xff0c;为什么需要智能指针 二&#xff0c;内存泄露的基本认识 1. 内存泄露分类 2. 常见的内存检测工具 3&#xff0c;如何避免内存泄露 三&#xff0c;智能指针的使用与原理 1. RAII思想 2. 智能指针 &#xff08;1. unique_ptr &#xff08;2. shared_…

CSS补充(下),弹性布局(上)

高级选择器 1.兄弟选择器 2.同时满足 div.bg{background-color: red;}p.bg{background-color: green;}spam.bg{background-color: blue;}注&#xff1a;选择器中间没有空格&#xff0c;有明确标识的选择器写在后面 3.各种伪类的应用 3.1作为第几个子元素 选择器:nth-child…

达梦数据库——如何查看数据库大字段中的数据内容

今天get到一个小知识点 分享给大家&#xff0c;如何在数据库查看大字段中的数据内容。 以下为演示步骤&#xff0c;简单易懂&#xff0c;操练起来吧 首先创建一个含有CLOB、TEXT的大字段测试表 create table "SYSDBA"."CS"("COLUMN_1" CLOB,&qu…

Xilinx 7系列 FPGA硬件知识系列(九)——FPGA的配置

1.3 端口配置 每组配置模式都有一组特定的接口管脚&#xff0c;它们跨越7系列FPGA上的一个或多个I/O BANK。7系列器件支持3.3V、2.5V、1.8V或1.5V的I/O配置&#xff0c;包括&#xff1a; BANK 0 中的JTAG管脚、BANK0中的专用配置管脚 BANK14和BANK15中与特定配置模式相关的管…

Windows下Node.js安装保姆级教程

一、Node.js 下载 访问Node.js官网&#xff0c;点击下载Node.js 下载完成后即可在下载文件中查看安装包 二、安装 一&#xff09;点击安装包开始安装&#xff0c;进入Weclcome界面点击Next 二&#xff09;勾选同意协议&#xff0c;点击Next 三&#xff09;根据需要选择安装路…

a-calendar的日历如何汉化

ant design中的日历默认拿来用时英文的&#xff0c;如何汉化&#xff0c;如下操作&#xff0c;在日历组件外面包一个<a-config-provider></a-config-provider> &#xff0c;如下操作&#xff1a; <template><a-config-provider :locale"zhCN"&…

合并有序数组

合并有序数组 题目描述&#xff1a;解法思路&#xff1a;解法代码&#xff1a;运行结果&#xff1a; 题目描述&#xff1a; 输入两个升序排列行的序列&#xff0c;将两个序列合并为一个有序序列并输出。 输入包含三行&#xff0c;第一包含两个正整数n, m&#xff0c;用空格分隔…

【Web安全】htaccess攻击

.htaccess攻击 文章目录 .htaccess攻击1. .htaccess文件2. 常见用法2.1. 自定义出错界面2.2. 强制文件执行方式2.3. PCRE绕过正则匹配2.4. php_value修改php设定2.5. php_value文件包含2.6. 把htaccess当作php 1. .htaccess文件 .htaccess是Apache网络服务器一个配置文件&#…

JavaWeb——014SpringBoot原理(配置优先级、Bean管理、SpringBoot原理)

SpingBoot原理 目录 SpingBoot原理1. 配置优先级2. Bean管理2.1 获取Bean2.2 Bean作用域2.3 第三方Bean 3. SpringBoot原理3.1 起步依赖3.2 自动配置3.2.1 概述3.2.2 常见方案3.2.2.1 概述3.2.2.2 方案一3.2.2.3 方案二 3.2.3 原理分析3.2.3.1 源码跟踪3.2.3.2 Conditional 3.2…

【Linux】第四十站:线程概念

文章目录 一、线程二、Linux中线程应该如何理解三、重新定义线程四、四谈进程地址空间&#xff08;页表相关&#xff09;五、Linux线程周边的概念1. 线程与进程切换2.线程优点3.线程缺点4.线程异常5.线程用途 一、线程 线程&#xff1a;是进程内的一个执行分支。线程的执行粒度…

事务失效问题

1&#xff0c;事务方法非public修饰 由于Spring的事务是基于AOP的方式结合动态代理来实现的。因此事务方法一定要是public的&#xff0c;这样才能便于被Spring做事务的代理和增强。 2&#xff0c;非事务方法调用事务方法 Service public class OrderService {public void creat…

Linux grep

文章目录 1. 基本用法2.字符转义3.二进制文件查找4.打印目标字段的附近行4. 多条件过滤5. 目录中过滤——用于在文件夹中筛选/排除指定后缀文件6.反向过滤——用于筛选7.只输出匹配内容——用于统计8. 筛选出包含字段的文件9.正则匹配10.管道和grep11.grep和wc/uniq/sort的合用…

【考研数学】李林《880》vs 李永乐《660》完美使用搭配

没有说谁一定好&#xff0c;只有适不适合自身情况&#xff0c;针对自身弱点选择性价比才最高。 两者侧重点不同&#xff0c;660适合强化前期&#xff0c;弥补基础的不足&#xff0c;880适合强化后期&#xff0c;题型全面&#xff0c;提高我们对综合运用知识的能力。 选择习题…

2.4_1 死锁的概念

文章目录 2.4_1 死锁的概念&#xff08;一&#xff09;什么是死锁&#xff08;二&#xff09;死锁、饥饿、死循环的区别&#xff08;三&#xff09;死锁产生的必要条件&#xff08;四&#xff09;什么时候会发生死锁&#xff08;五&#xff09;死锁的处理策略 总结 2.4_1 死锁的…

MySQL 存储过程(超详细)

一、什么是存储过程&#xff1f; 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。换句话说&#xff0c…