设计模式-中介者模式

2023-09-17 01:07:35

每次乘坐高铁出行时,我都会像这样一个问题:这么多列车都可能通过这条轨道,会不会存在冲突的可能呢?同样的,飞机的起飞和降落时对于道路的选择也会有冲突的可能。这些情况都会造成可怕的后果, 而阻止这种情况发生的就是机场调度中心。飞机在起飞和降落前都会请求机场调度中心,由机场调度中心来负责协调飞机、地面道路、摆渡车辆等。因此,机场调度中心就属于机场的总指挥,会统一协调各单位的运行状态,使得飞机能够安全、顺利起飞和降落。
我们试想以下,如果没有机场调度中心的话,飞机在起飞时如何判断应该从哪个通道加速、起飞呢?飞机的机长需要确保乘客全部已经登机,还需要询问其他飞机是否在某车道内,或即将在某车道降落,还需要确保地面道路准备就绪,还需要确保天气的良好状况等等。可以看出,如果没有机场调度中心来负责这些起飞之外的其他情况的话,机长或机组人员会忙的不可开交,并且存在飞机道路冲突的风险。机场调度中心的存在使得飞机和其他影响单位不需要直接交互,是否可起飞和降落,完全由调度中心来调停和通知。类似这种降低各单位之间相互交互的耦合性的解决方案就是我们今天所要讲解的中介模式。

一、中介者模式概念

中介者模式,也称之为调停模式,其一般定义如下:

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

中介者模式就是为了解决各种对象之间繁杂的交互关系,将对象交互关系抽离出来,由中介对象负责这项功能。各对象之间更加关注自身的业务逻辑实现,而对象之间交互的业务逻辑有中介对象负责
一般情况下,对象之间的耦合是正常的,在迪米特法则那篇文章中,我们说到“在单一职责原则基础上吗,应该强耦合的朋友类之间所对应的业务逻辑也是强耦合的”。但是如果业务逻辑上,对象需要与很多对象之间互相交互,就可能会影响到单一职责原则,进而影响了业务稳定性。因此,在对象交互繁杂的情况下,可以通过中介者模式来拆分原则-将对象交互由中介对象负责
这里一定要区分“中介”和“代理”的区别。在代理模式的那篇文章中,我们提到房产中介其实也是代理的一种,这很容易导致很多同学对中介模式的理解偏差。代理是在原业务逻辑的基础上增加代理功能,而中介是为了协调不同业务对象之间的交互。在房产中介案例中,其提供给房东与购买者的中介信息(卖家与买家信息)就属于中介行为,而在实际购买过程中,中介人员协助购买者或卖家办理房产手续这部分就属于代理行为。
概念总结:

  • 中介模式是为解决对象之间交互繁杂的问题。
  • 中介模式使得业务对象更加关注其业务逻辑,业务对象交互由中介对象负责。

二、应用实践

为了更好的理解、应用中介者模式,本章通过具体的案例来实现下中介模式的应用场景。
案例背景就是关于上文提到的机场调度中心的场景。为了简化代码,突出重点,其中涉及的对象包括多架飞机(可调配对象),机场调度中心。机场调度中心需要负责协调多架飞机之间占用跑道的分配问题,飞机之间互相不存在耦合关系。
首先设计一个基本飞机类如下,这是一个抽象类。

/**
 * 飞机基本抽象类
 * (机场的飞机必须是可调度飞机,因此这里不可实例化)
 */
@Getter
public abstract class Airplane {
    private String flightNumber;    // 飞机航班编号
    private String departure;       // 起飞地
    private String destination;     // 目的地
    private long departureTime;     // 起飞时间
    private long arrivalTime;       // 降落时间
    private long duration;          // 航行时长

    public void initAirplane() {
        // 初始化航班属性...
    }

    // 飞机起飞
    public abstract void takeOff();

    // 飞机降落
    public abstract void land();
}

为支持机场调度中心,设计可调度飞机类:

/**
 * 可调配飞机类型
 */
public class DispatchableAirplane extends Airplane implements Dispatchable {

    private AirportDispatcher dispatcherIns;

    public DispatchableAirplane() {
        initAirplane();             // 初始化基本航班属性
        this.setDispatcher(AirportDispatcher.dispatcherIns);    // 设置中介对象-机场调度中心
    }

    @Override
    public void takeOff() {
        // 申请跑道使用
        Long permissionForRunway = dispatcherIns.assignRunwayNumber(this);
        if(permissionForRunway == -1) {
            System.out.println("暂无跑道,无法起飞");
            return;
        }
        System.out.println("起飞喽~~,跑道编号:" + permissionForRunway);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        dispatcherIns.cancelRunway(permissionForRunway);    // 起飞完成,告知调度中心以释放跑道
    }


    @Override
    public void land() {
        // 申请跑道使用
        Long permissionForRunway = dispatcherIns.assignRunwayNumber(this);
        if(permissionForRunway == -1) {
            System.out.println("暂无跑道,无法降落");
            return;
        }
        System.out.println("即将降落~~,跑道编号:" + permissionForRunway);
        dispatcherIns.cancelRunway(permissionForRunway);    // 降落完成,告知调度中心以释放跑道
    }


    @Override
    public void setDispatcher(AirportDispatcher dispatcherIns) {
        this.dispatcherIns = dispatcherIns;
    }
}

可调度飞机类还实现了Dispatchable方法,用于表示其可被哪个调度中心调配:

public interface Dispatchable {
	void setDispatcher(AirportDispatcher dispatcherIns);
}

其中AirportDispatcher就是飞机调度中心,调度中心应该是一个单例对象,因此其暂时不考虑抽象层。代码如下:

/**
 * 机场调度中心类,注意这里应是单例
 */
public class AirportDispatcher {

    public static final AirportDispatcher dispatcherIns = new AirportDispatcher();

    // 最大跑道编号
    private final Long maxRunwayNum;

    // 所有可配跑道编号的占用情况
    private final Map<Long, Dispatchable> runwayMaps = new ConcurrentHashMap<>();;

    private AirportDispatcher() {
        maxRunwayNum = 100L;
    }

    // 向外透出可分配跑道编号,-1表示暂无跑道
    public Long assignRunwayNumber(Dispatchable reqDispatch) {
        for (long no = 0; no < maxRunwayNum; no++) {
            if(runwayMaps.containsKey(no)) {
                continue;
            }

            Dispatchable dispatchable = runwayMaps.putIfAbsent(no, reqDispatch);
            if(dispatchable == null) {
                return no;
            }
        }
        return -1L;
    }

    public void cancelRunway(Long RunwayNum) {
        runwayMaps.remove(RunwayNum);
    }

}

Client代码如下:

public class Client {
    public static void main(String[] args) {
        // 创建可调配飞机航班
        DispatchableAirplane dispatchPlane1 = new DispatchableAirplane();
        DispatchableAirplane dispatchPlane2 = new DispatchableAirplane();

        new Thread(() -> {
            try {
                dispatchPlane1.takeOff();
                Thread.sleep(dispatchPlane1.getDuration());	// 飞行中...
            } catch (Exception e) {
                // 不存在的异常
            } finally {
                dispatchPlane1.land();
            }

        }).start();
        new Thread(() -> {
            try {
                dispatchPlane2.takeOff();
                Thread.sleep(dispatchPlane2.getDuration());	// 飞行中...
            } catch (Exception e) {
                // 不存在的异常
            } finally {
                dispatchPlane2.land();
            }
        }).start();
    }
}

在这里插入图片描述

从上面可以看出,AirportDispatcher主要负责的就是多架飞机(Dispatchable实例)对跑道的协调工作。Dispatchable实例之间不需要互相耦合交互。在扩展性方面,加入以上示例中机场调度中心还要负责地勤人员(GroundStaff )调度,那么GroundStaff也应该实现Dispatchable方法,因此这种设计下,几乎不需要改动原有代码,只需要实现GroundStaff的内部逻辑即可。
中介模式的优点:

  • 进一步划分了类的职责,使得代码易读性、维护性更好
  • 减少了类间的依赖,依赖或交互只存在于业务对象与中介对象之间

中介模式的缺点:

  • 当对象关系种类越来越多时,会使得中介对象的逻辑也很复杂。【算缺点么,不使用中介模式岂不是更糟糕】
更多推荐

惯性动捕+数据手套,让“虚拟”触手可及

当今,虚拟现实技术已经从科幻电影走进现实生活。在数字化时代,惯性动作捕捉系统与数据手套的结合使用,带给我们全新的虚拟互动体验,使虚拟世界更能够“触手可及”。01惯性动作捕捉系统FOHEARTMAGIC是一款高性能的惯性动作捕捉系统。它由17个惯性传感器和数据接收器组成。每个惯性传感器都内置了三轴加速度计、三轴磁力计和三

【看表情包学Linux】软硬链接 | 软连接数 | 创建软硬链接 | 动静态库 | 生成静态库 | 生成动态库

🤣爆笑教程👉《看表情包学Linux》👈猛戳订阅🔥💭写在前面:上一章我们讲解了inode,为文件系统收了尾,这几章我们充分地讲解完了文件系统的知识点,现在我们开始开始学习软硬链接了。如果没有文件系统的铺垫,想直接理解软硬链接难免有些困难。但我们讲完了文件系统再去理解软硬链接,你就会发现没有那么难,因为我们是从底

MyBatis 高级使用

文章目录动态SQL语句ifchoosetrimforeach批量操作批量插入批量更新批量删除BatchExecutor关联查询嵌套查询延迟加载分页操作逻辑分页物理分页MyBatisGenerator添加配置文件添加插件生成通用Mapper方式一方式二MyBatis-Plus动态SQL语句动态SQL是MyBatis的强大

DSI及DPHY的学习知识点

目录1.DPHY的输出差分clk是双沿有效2.LP和Escape这些低功耗传输是单端的3.ContentionDetection(竞争检测)4.双向data-lane,可以选择只支持双向HS或Escape5.传输数据和命令只能在HS和Esc的LPDT6.正向Esc必须支持ULPS和Triggers7.ULPS是什么样的

Unity——模拟AI视觉

人类的视觉系统有以下几个特点:距离有限。近处看得清,远处看不清容易被遮挡。不能穿过任何不透明的障碍物视野范围大约为90度。实现正前方信息丰富,具有色彩和细节;实现外侧的部分只有轮廓和运动信息注意力有限。当关注某个具体的方位或物体时,其他部分被忽略,如魔术中的障眼法总是能骗过观众对AI视觉的模拟就是基于以上这些基本特点,

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(三)

目录前言总体设计系统整体结构图系统流程图运行环境模块实现1.数据预处理2.数据增强3.模型构建4.模型训练及保存1)模型训练2)模型保存5.模型评估相关其它博客工程源代码下载其它资料下载前言本项目依赖于Keras深度学习模型,旨在对手语进行分类和实时识别。为了实现这一目标,项目结合了OpenCV库的相关算法,用于捕捉手

c#扩展包-Stateless

准备Stateless是一个有限状态机扩展包。在c#项目中可以直接通过NuGet安装。使用他需要先用枚举写好你所有可能的状态和子状态。例如移动,下蹲,空闲,跳跃,游泳,奔跑,走路。其中,奔跑和走路是移动的子状态。然后需要写触发器。所有状态转换必须要一个触发器。所以你需要把所有的时机都精确描述,并且哪怕只有一个地方用到也

详细介绍Webpack5中的Plugin

Plugin的作用插件Plugin可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。Plugin的工作原理webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成

前端深入理解JavaScript函数式编程

🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!目录引言1.什么是函数式编程?2.纯函数和不可变性3.高阶函数4.函数组合5.柯里化6.递归7.函数式编程的优势8.结语引言函数式编程(FunctionalProgramming)是一种编程范式,它将计算机程序视为

豆瓣图书评分数据的可视化分析

导语豆瓣是一个提供图书、电影、音乐等文化产品的社区平台,用户可以在上面发表自己的评价和评论,形成一个丰富的文化数据库。本文将介绍如何使用爬虫技术获取豆瓣图书的评分数据,并进行可视化分析,探索不同类型、不同年代、不同地区的图书的评分特征和规律。概述本文的主要步骤如下:使用scrapy框架编写爬虫程序,从豆瓣图书网站抓取图

使用 docker-compose 构建你的项目

使用docker-compose构建你的项目1.Docker1.1安装1.2docker-compose2准备项目2.1初始化一个node项目4.准备一个Dockerfile文件5.构建镜像3.docker-compose构建3.1配置docker-compose.yml文件3.2编排多个服务重新构建镜像--force

热文推荐