从零开始学习Java设计模式 | 行为型模式篇:中介者模式

在本讲,我们来学习一下行为型模式里面的第七个设计模式,即中介者模式。

概述

在学习中介者模式之前,我们先来看下面这段描述。

一般来说,同事类之间的关系是比较复杂的(其实,这儿所说的同事类就是中介者模式里面的一个角色,而在这里,你可以先把它当作一个普通的类来看待),多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,既不利于类的复用,也不稳定。例如,下图中有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响,因为对象1所属类和对象2、对象4、对象5、对象6所属类之间是有关联关系的。如果对象2发生变化,那么将会有5个对象受到影响。所以,这种错综复杂的关系会让我们在后期维护起来特别特别麻烦,也就是说,同事类之间直接关联的设计是不好的。

在这里插入图片描述

那么我们应该如何来改进呢?这时不妨使用一下中介者模式,因为使用中介者模式的话,可以使得同事类之间的关系成为星型的结构,也就是我们下图右侧所展示出来的星型结构。

在这里插入图片描述

从上图右侧部分可以看到,每一个同事类,它只和中介者进行一个相互关联,而它们相互之间是不需要进行任何关联的,这样,任何一个类的变动,只会影响到类本身以及中介者,也就是说,对象1所属类里面的业务逻辑发生变化了的话,那么对象1所属类以及中介者要发生一个改变,而对于其他对象的所属类则是不需要进行改动的,这样就减小了系统的耦合。

一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为,这个专门的类就是中介者。

下面我再举一个例子让大家好好理解一下。

如果我们攒够首付了,想要去买一套属于自己的房子,那么我们应该怎么办呢?我们作为一个购房者,是不会直接去找对应的房主的,因为房主有很多,我们找起来会非常麻烦,一个一个房主去找,会累坏我们,正确的做法就是去找房屋中介。对于房主而言,他只需要把自己房子的信息交给中介进行管理即可,而对于购房者来说,他只需要去找对应的房屋中介就行,房屋中介会根据他的需求去找对应的房主协商,并给购房者一些比较不错的建议。

在这里插入图片描述

如果此时购房者的需求发生了变化,那么他并不需要把他的需求告知给所有的房主,而只需要告知给房屋中介即可,房屋中介会根据他的需求帮他去选择合适的房子,这就是中介者模式。

那什么是中介者模式呢?下面我们来看一下它的概念。

中介者模式又叫调停模式,它定义了一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

上述中介者模式的概念,大家看着可能挺懵的,这没关系,下面我给大家稍微解释一下。

就拿上述购房者买房的案例来说,房屋中介就是中介者角色,它封装了房主以及购房者之间的交互关系,这样就会使得房主和购房者之间的耦合变得松散,要是没有房屋中介的话,购房者买房就只能去找房主了,一旦看的房子他不满意,他还需要再去找另外一个房主,还不满意的话,还得继续去找其他房主,那么这就非常耗时、耗资源了。并且对于房屋中介来说,它可以独立地改变购房者和房主之间的交互,因为购房者和房主之间要沟通的话,就得经过房屋中介。其实,从这里就可以看出,购房者和房主就是上面我所说的同事类。

理解了中介者模式的概念之后,接下来,我们就来看看中介者模式的结构,也就是它里面所拥有的角色。

结构

中介者模式包含以下主要角色:

  • 抽象中介者(Mediator)角色:它是中介者的接口(当然了,对于该角色而言,你既可以把它定义成接口,也可以把它定义成抽象类),提供了同事对象注册与转发同事对象信息的抽象方法,很显然,该方法由具体中介者角色来实现。

  • 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个List集合来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。

    看到这个角色,大家是不是想到了房屋中介啊!正是有了房屋中介,购房者买房时,就简单多了,房屋中介会根据购房者的需求去选择合适的房源以及找到对应的房主。

  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。

    注意了,在抽象同事类角色里面,一定要保存中介者对象。原因是很明显的,就拿购房者买房的案例来说,不仅房主要记住对应的中介,而且购房者也要记住对应的中介,不然的话,购房者又该如何去找房呢?

  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

    就拿购房者买房的案例来说,购房者要买房,房屋中介会根据他的需求去找对应的房主,也就是说购房者只需把他的需求告诉房屋中介即可,这样房屋中介就会根据他的需求去找对应房主商谈诸事宜。

中介者模式案例

接下来,按照惯例我们通过一个案例来让大家再去理解一下中介者模式的概念,以及它里面所包含的角色,这个案例就是租房。

分析

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。因此,房屋中介充当的是租房者与房屋所有者之间的中介者,而对于租房者与房屋所有者来说,他们都是属于同事类。

接下来,我们再来看一下下面的这张类图。

在这里插入图片描述

可以看到,有Mediator这么一个抽象类,很明显,该类充当的就是中介者模式里面的抽象中介者角色,它里面声明有一个方法,即和同事类进行交互的方法。

然后,我们再来看一看MediatorStructure类,该类充当的是中介者模式里面的具体中介者角色。很明显,该类继承了以上抽象中介者角色类,而且该类里面还聚合了HouseOwner类和Tenant类的对象,它俩分别代指的是房主和租房者。把这俩对象聚合进来之后,我们肯定是要给它们提供对应的getter和setter方法的,除此之外,该类里面还重写了父类中和同事类进行交互的方法。

接着,我们再来看一个类,即Person,该类充当的是中介者模式里面的抽象同事类角色。可以看到,该类里面有两个成员变量,一个是String类型的name变量,一个是中介者类型(即Mediator类型)的变量,所以从上图中你也能看到该类和Mediator类之间是聚合关系。除此之外,我们还能看到,该类还提供了一个有参的构造方法。

最后,我们来看一下以上抽象同事类的两个子类,一个是租房者类,即Tenant,一个是房屋拥有者类,即HouseOwner。这俩类除了定义各自对应的构造方法之外,还定义有和中介者进行交互的constact方法以及获取另外一个同事对象传递过来的信息的getMessage方法,当然,该信息是由中介者传递过来的。

分析完以上类图之后,接下来我们就要编写代码实现以上租房案例了。

实现

首先,打开咱们的maven工程,并在com.meimeixia.pattern包下新建一个子包,即mediator,也即实现以上租房案例的具体代码我们是放在了该包下。

然后,创建抽象中介者类,这里我们就命名为了Mediator。

package com.meimeixia.pattern.mediator;

/**
 * 抽象中介者类
 * @author liayun
 * @create 2021-09-17 20:31
 */
public abstract class Mediator {

    /**
     * 进行沟通的方法
     * @param message:和中介沟通的信息
     * @param person:Person类是同事类顶层父类,这儿具体是哪个同事类和中介去沟通呢?这个得在具体中介者类中去明确
     */
    public abstract void constact(String message, Person person);

}

接着,创建抽象同事类,这里我们就命名为Person了。

package com.meimeixia.pattern.mediator;

/**
 * 抽象同事类
 * @author liayun
 * @create 2021-09-17 20:34
 */
public abstract class Person {

    protected String name; // 租房者或者房主的姓名
    protected Mediator mediator; // 中介者

    public Person(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

}

紧接着,创建具体同事类。这里我们先创建租房者类,名称不妨就叫作Tenant。

package com.meimeixia.pattern.mediator;

/**
 * 具体的同事角色类
 * @author liayun
 * @create 2021-09-17 20:41
 */
public class Tenant extends Person {

    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }

    // 和中介联系(沟通)
    public void constact(String message) {
        mediator.constact(message, this); // 这儿究竟是谁和中介去沟通呢?肯定是当前类的对象,是不是啊!所以这儿直接把this传递进来就可以了
    }

    /**
     * 获取信息
     * @param message:房主反馈回来的信息
     */
    public void getMessage(String message) {
        System.out.println("租房者" + name + "获取到的信息是:" + message);
    }

}

再创建房屋拥有者类,这里我们就命名为HouseOwner了。

package com.meimeixia.pattern.mediator;

/**
 * 具体的同事角色类
 * @author liayun
 * @create 2021-09-17 20:41
 */
public class HouseOwner extends Person {

    public HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }

    // 和中介联系(沟通)
    public void constact(String message) {
        mediator.constact(message, this); // 这儿究竟是谁和中介去沟通呢?肯定是当前类的对象,是不是啊!所以这儿直接把this传递进来就可以了
    }

    /**
     * 获取信息
     * @param message:租房者反馈回来的信息
     */
    public void getMessage(String message) {
        System.out.println("房主" + name + "获取到的信息是:" + message);
    }

}

抽象同事类和两个具体同事类创建完毕之后,接下来,我们来创建具体中介者类,这里我们不妨就起名为MediatorStructure。

package com.meimeixia.pattern.mediator;

/**
 * 具体的中介者角色类
 * @author liayun
 * @create 2021-09-17 20:53
 */
public class MediatorStructure extends Mediator {

    // 聚合房主和租房者对象。为什么要聚合这俩对象呢?因为中介机构必须知道所有房主和租房者的信息!
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    public Tenant getTenant() {
        return tenant;
    }

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    @Override
    public void constact(String message, Person person) {
        // 中介机构和谁进行沟通,是不是得进行判断一下啊?
        if (person == houseOwner) { // 若中介机构和房主进行沟通,则最终租房者应获得信息
            tenant.getMessage(message);
        } else { // 若中介机构和租房者进行沟通,则最终房主应获得信息
            houseOwner.getMessage(message);
        }
    }

}

最后,创建一个客户端类用于测试。

package com.meimeixia.pattern.mediator;

/**
 * @author liayun
 * @create 2021-09-17 23:41
 */
public class Client {
    public static void main(String[] args) {
        // 创建中介者对象
        MediatorStructure mediator = new MediatorStructure();

        // 创建租房者对象
        Tenant tenant = new Tenant("李四", mediator);
        // 创建房主对象
        HouseOwner houseOwner = new HouseOwner("张三", mediator);

        // 中介者要知道具体的房主和租房者
        mediator.setTenant(tenant);
        mediator.setHouseOwner(houseOwner);

        // 租房者去跟中介沟通,这样中介就会将沟通的信息直接传递给具体的房主,房主就能获取到这个信息了。
        tenant.constact("我要租三室一厅的房子!!!");
        // 房主获取到信息之后,他也得和中介沟通,中介再把沟通的信息告诉给租房者
        houseOwner.constact("我这里有三室一厅的房子,你要租吗?");
    }
}

此时,运行以上客户端类,打印结果如下图所示,可以看到租房者首先会将他的需求告诉给房屋中介,而房屋中介会再将该要求告知给具体的房主,这样,房主就知道租房者的诉求了。然后,房主再反馈给房屋中介,而房屋中介会将该反馈信息告知给租房者,于是,租房者就能知道这个反馈信息了。

在这里插入图片描述

以上就是我们要实现的租房案例。在该案例中,你会发现,租房者和房主这俩对象现在是没有任何关联关系的,它俩都是通过房屋中介去关联的。因此,如果租房者的需求发生了一个变化的话(或者租房者类里面的业务逻辑进行了一个改变),那么他只需要去告知给对应的房屋中介即可,而且此时对于其他的房主类来说,代码是不需要进行改动的。

中介者模式的优缺点以及使用场景

接下来,我们就来看一看中介者模式的优缺点以及使用场景。

优缺点

优点

关于中介者模式的优点,这里我总结了三点。

  1. 松散耦合。

    中介者模式通过把多个同事对象之间的交互封装到中介者对象里面(也就是说,多个同事对象之间的关联是在中介者对象里面体现出来的),从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样"牵一处而动全身"了。

    大家在这里一定要注意,如果某个同事类里面的逻辑发生了变化,那么相应的我们可能就要去修改中介者角色类的代码了,但是,大家要知道此时我们是不需要去修改和该同事类关联的其他同事类的代码的。

  2. 集中控制交互。

    多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了。当然,如果是已经做好的系统,那么就扩展中介者对象,而各个同事类则不需要做修改。也就是说,此时我们可以再去创建一个抽象中介者类的子类,而对于其他的同事类而言,基本上都不需要修改,因为对于同事类来说,它依赖的是抽象。

    在这里插入图片描述

    说到集中控制交互,我就要多说一点了,如果大家想做一个聊天工具的话,那么就可以使用中介者模式来实现了。当然,这里我就不具体实现了,有兴趣的同学可以私下自己来试着实现一下。

  3. 一对多关联转变为一对一的关联。

    没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成了双向的一对一这种关系,这会让对象的关系更容易理解和实现。

缺点

当同事类太多时,中介者的职责将会很大,它会变得复杂而庞大,以至于系统难以维护。

在上述租房案例里面,同事类也就两个,即租房者和房主,他俩之间相互关联的关系相对来说还是比较简单的,但是,一旦同事类特别多的话,那么中介者的责任将会变得很大,具体体现在具体中介者类里面的方法实现起来会比较麻烦。

使用场景

只要出现如下几个场景,我们就可以去考虑一下能不能使用中介者模式了。

  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。相信大家也看到了,正是基于这一场景,在上述租房案例中我们才使用了中介者模式。

  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

    其实,这儿说的是想创建一个中介者角色类,因为中介者模式里面最重要的就是中介者角色,它在整个模式中起到了一个关键性的作用。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李阿昀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值