设计模式——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,有电灯号和灯笼号两部地铁,同时规定:
-
两部地铁都是沿着A->B->C这个方向往前移动
-
电灯号从站台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("还有车,无法驶入");}
}
这段代码有两个问题:
-
里面出现了可以抽离出来的部分,也就是move方法
-
我们向 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框架实现的基础
但是这种平台便利性是要付出代价的
随着系统的扩大,这个负责对象交互的平台一定会愈发复杂,而这部分 复杂 其实就是原本各自对象之间要进行的交互。也就是说,使用平台并不是彻底消灭了 复杂,而是把他们集中起来处理
这是符合设计原则的,因为其中有一条是这样写的:
变化隔离原则
找出应用中可能需要变化的地方,把他们独立出来,不要和那些不需要变化的代码混合在一起
依据这个原则,所以我们把对象之间的交互和对象自身要处理的业务进行隔离。因为对象之间的交互总是充满不确定性的,而对象自身的业务通常是在编码时就已经确定的
姑妄言之
说白了,中介者其实就是一个跟所有人都有关联的对象。那其实我们的古人早就提到过中介者这样的概念,那时的中介者,通常是我们头顶的月亮。李白就写过:我寄愁心与明月,随风直到夜郎西 这样的诗句,其实就是让大家都共享的月亮帮他传递信息嘛。
所以哪怕人真的是孤岛,又怎么可能真的那么孤单。只要仰望夜空,就一定有人此时此刻和你一起在同一片星空下仰望同一个月亮
万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容
相关文章:

设计模式——2_4 中介者(Mediator)
我寄愁心与明月,随风直到夜郎西 ——李白《闻王昌龄左迁龙标遥有此寄》 文章目录 定义图纸一个例子:怎么调度一组地铁站台和地铁开车指挥中心 碎碎念中介者和表单平台思想但是这种平台便利性是要付出代价的变化隔离原则 姑妄言之 定义 用一个中介者对象…...

C语言教程(一)——输出、数据类型、表达式、条件判断、循环
一个C语言程序必须包含以下代码: int main(void){return 0; } 这是整个C语言程序的入口 输出 输入以下代码并运行(注意分号): #include <stdio.h>int main(void){printf("Hello World\n");return 0; }可以看…...

Prompt Engineering、Finetune、RAG:OpenAI LLM 应用最佳实践
一、背景 本文介绍了 2023 年 11 月 OpenAI DevDay 中的一个演讲,演讲者为 John Allard 和 Colin Jarvis。演讲中,作者对 LLM 应用落地过程中遇到的问题和相关改进方案进行了总结。虽然其中用到的都是已知的技术,但是进行了很好的总结和串联…...

[C语言]——分支和循环(4)
目录 一.随机数生成 1.rand 2.srand 3.time 4.设置随机数的范围 猜数字游戏实现 写⼀个猜数字游戏 游戏要求: (1)电脑自动生成1~100的随机数 (2)玩家猜数字,猜数字的过程中,根据猜测数据的⼤…...

【LeetCode】392. 判断子序列(简单)——代码随想录算法训练营Day54
题目链接:392. 判断子序列 题目描述 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"…...

1. Typescript入门
TS 基础 Typescript 在线编译平台 基础类型 boolean、number 和 string 类型 boolean let isHandsome: boolean true赋值与定义的不一致,会报错,静态类型语言的优势就体现出来了,可以帮助我们提前发现代码中的错误。 let isHandsome: …...

【Git】merge时报错:refusing to merge unrelated histories
文章目录 一、问题二、解决办法1、将feature分支的东西追加到master分支中2、将feature里的东西直接覆盖到master分支中 一、问题 今天将feature分支合并到master时报错:refusing to merge unrelated histories(拒绝合并无关历史) 报错原因&…...

树状数组+离散化求逆序对超详细讲解!
树状数组离散化求逆序对 用一个数组 w [ ] w[] w[]来记录遍历到当前数时,每个数出现的次数 由于只关心每个数前边有多少个数比他大,遍历到 i i i时,求大于 a [ i ] a[i] a[i]的数有多少个,就是对 [ a [ i ] , n ] [a[i], n] [a[i…...

《解密云计算:企业之选》
前言 在当今数字化时代,企业面临着巨大的数据处理压力和信息化需求,传统的IT架构已经无法满足日益增长的业务需求。在这样的背景下,越来越多的企业开始转向云计算,以实现灵活、高效和可扩展的IT资源管理和利用。 云计算 云计算是…...

地址分词 | EXCEL批量进行地址分词,标准化为十一级地址
一 需求 物流需要对用户输入地址进行检查,受用户录入习惯地址可能存在多种问题。 地址标准化是基于地址引擎和地址大数据模型,自动将地址信息标准化为省、市、区市县、街镇、小区、楼栋、单元、楼层、房屋、房间等元素,补充层级缺失数据、构建…...

KubeSphere平台安装系列之二【Linux单节点部署KubeSphere】(2/3)
**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere(亲测–实操完整版)】(1/3) 【Linux单节点部署KubeSphere】(2/3) 【Linux多节点部署KubeSphere】(3/3) **《KubeS…...

网络安全: Kali Linux 使用 docker-compose 部署 openvas
目录 一、实验 1.环境 2.Kali Linux 安装docker与docker-compose 3.Kali Linux 使用docker-compose方式部署 openvas 4. KaliLinux 使用openvas 二、问题 1. 信息安全漏洞库 2.信息安全漏洞共享平台 3.Windows 更新指南与查询 4.CVE 查询 5.docker-compose 如何修改o…...

【学习考试心得】在誉天学习考试RHCE9.0的体验
作为华中第一位参加RHCE9.0线上考试的考生,很荣幸能来写这个心得,和大家分享一下线上的考试的一些体验。 一、学习体验 首先在红帽课程的学习中,跟着杨峰老师的脚步,整个学习过程中都非常有意思。杨峰老师充满磁性的声音和小王老师…...

Flip Clock(not good)
最近体验了一下iOS的翻页时钟app,很想自己做一个,但是效果不好 public class main {public static void main(String[] args) {//psvmnew MyFrame();} }import javax.swing.*; import java.awt.*; import java.io.File; import java.io.IOException; im…...

目标检测——摩托车头盔检测数据集
一、简介 首先,摩托车作为一种交通工具,具有高速、开放和稳定性差的特点,其事故发生率高,伤亡率排在机动车辆损伤的首位。因此,摩托车乘员头盔对于保护驾乘人员头部安全至关重要。在驾乘突发状况、人体受冲击时&#…...

Windows 安装 Xinference
Windows 安装 Xinference 0. 引言1. 创建虚拟环境2. 安装 pytorch3. 安装 llama_cpp_python4. 安装 chatglm-cpp5. 安装 Xinference6. 设置 model 路径7. 启动 Xinference8. 查看 Cluster Information 0. 引言 Xorbits Inference(Xinference)是一个性能…...

静态时序分析:SDC约束命令set_case_analysis详解
相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定值 指定端口/引脚列表 简单使用 set_case_analysis命令用于对电路进行特定模式的设定,例如对于一个工作在正常模式下的芯片,…...

C++ · 代码笔记4 ·继承与派生
目录 前言010继承与派生简单例程020多级继承030使用using关键词更改访问权限040隐藏050派生类与基类成员函数同名时不构成重载060使用多级继承展示成员变量在内存中的分布情况071派生类在函数头调用基类构造函数072构造函数调用顺序080构造函数与析构函数的调用顺序091多重继承…...

解决uni-app中使用webview键盘弹起遮挡input输入框问题
这个平平无奇的回答,可能是全网最靠谱的解决方案。 这里我用的是vue3 setup .vue文件的方式 <view> <web-view :fullscreen"false" :webview-styles"{top: statusBarHeight40,height:height,progress: {color: green,height:1px } }"…...

Java注解介绍
Java注解 注解介绍元注解RetentionTargetDocumentedInherited接口类测试结果 注解介绍 Java注解(Annotation)是一种元数据(Metadata)的形式,它可以被添加到Java代码中的类、方法、变量、参数等元素上,以提…...

万字详解,Java实现低配版线程池
文章目录 1.什么是线程池2.线程池的优势3.原理4.代码编写4.1 阻塞队列4.2 ThreadPool线程池4.3 Worker工作线程4.4 代码测试 5. 拒绝策略5.1 抽象Reject接口5.2 BlockingQueue新增tryPut方法5.3 修改ThreadPool的execute方法5.4 ThreadPool线程池构造函数修改5.5 拒绝策略实现1…...

挂耳式蓝牙耳机哪家的好用?购买耳机前必须了解的几大要点
随着健康意识的提升,越来越多的人开始热衷于运动。运动不仅能够增强体质,对于我们这些忙碌的上班族而言,它也是一种极佳的减压方式。经过一天的辛勤工作,能够在户外跑步,让汗水带走压力,实在是一种享受。在…...

CSS文本属性
CSS文本属性 1.文本颜色2.文本间距3. 文本修饰4 .文本缩进5.文本对齐_水平6.行高7. vertical-align 1.文本颜色 属性名:color作用:控制文字的颜色。可选值: 颜色名rgb或rgbaHEX或HEXA (十六进制)HSL或HSLA 开发中常用…...

MySQL篇—执行计划之覆盖索引Using index和条件过滤Using where介绍(第三篇,总共三篇)
☘️博主介绍☘️: ✨又是一天没白过,我是奈斯,DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux,也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章,并且也会默默的点赞收藏加关注❣…...

最短路径(2.19)
目录 1.网络延迟时间 弗洛伊德算法 迪杰斯特拉算法 2. K 站中转内最便宜的航班 3.从第一个节点出发到最后一个节点的受限路径数 4.到达目的地的方案数 1.网络延迟时间 有 n 个网络节点,标记为 1 到 n。 给你一个列表 times,表示信号经过 有向 边的…...

vue 总结
1.vue 的生命周期 1. es6 2. vue 基本属性指令 <template><div><!--<h1>vue基本指令的使用方式</h1><a :href"url">v-bind使用链接</a><img :src"srcUrl" /><div>解决闪烁问题<p v-cloak>{{…...

深入理解TCP/IP协议:互联网通信的核心
深入理解TCP/IP协议:互联网通信的核心 在数字化时代,TCP/IP协议是支撑全球互联网通信的基石。它不仅负责数据的传输和路由,还确保了信息传递的准确性和完整性。本文将深入探讨TCP/IP协议的工作原理、结构以及它在网络编程中的应用。 TCP/IP…...

Python数据处理实战(4)-上万行log数据提取并作图进阶版
系列文章: 0、基本常用功能及其操作 1,20G文件,分类,放入不同文件,每个单独处理 2,数据的归类并处理 3,txt文件指定的数据处理并可视化作图 4,上万行log数据提取并作图进阶版&a…...

JavaWeb Tomcat启动、部署、配置、集成IDEA
web服务器软件 服务器是安装了服务器软件的计算机,在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目。 Web服务器是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序…...

关于Vue3的一些操作
1. 设置浏览器自动打开 在package.json 中设置 dev: vite --open 2.给src文件夹配置别名 在vite.config.ts配置文件中添加以下内容 3. 如果2中有红色波浪线的问题 ***安装一个文件包***npm install types/node3. 在tsconfig.json配置文件中,找到配置项compi…...