设计模式-中介者模式

257 阅读4分钟

# 中介者模式

1.简介

中介者模式是一种行为设计模式,它能减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。

面向对象设计鼓励将行为分布到各个对象中。但这种分布可能会导致多个对象之间需要相互交互,形成紧密耦合不利于对象的修改和维护。虽然将一个系统分割成许多对象通常可以增强其复用性,但是对象间的互相连接增多又会降低复用性,使其改动困难。

中介者模式通过引入一个中介对象,形成一个以中介者为中心的星形结构。某一个对象不再通过直接的联系与另一个对象发生相互作用,而是让所有的对象都只和中介者对象交互,从而实现了对象之间的解耦。中介者对象的存在保证了结构上的稳定,不会因为新对象的引入、或者是旧对象的更改造成大量的修改工作。

中介者类承担了两方面的职责:

  • 中转作用(结构性):通过中介者提供的中转作用,各个组件对象就不再需要显式引用其他组件。
  • 协调作用(行为性):中介者可以进一步对组件之间的关系进行封装,对于组件来说可以一致的和中介者进行交互,而不需要指明中介者具体需要怎么做。中介者根据封装在自身内部的协调逻辑,对组件的请求进行处理,将组件之间的关系行为进行分离和封装。

2.UML图

中介者模式1.png

  • 组件(Component):是各种包含业务逻辑的类。每个组件都有一个指向中介者的引用,该引用被声明为中介者接口类型。组件并不知道中介者实际所属的类,因此你可以通过将其连接到不同的中介者已使其能在其他程序中复用。
  • 中介者(Mediator):接口声明了与组件交流的方法,但通常仅包括一个通知方法,组件可将任意上下文(包括其自身)作为该方法的参数,这样接受组件和发送者类才不会耦合。
  • 具体中介者(Concrete Mediator): 封装了多种组件间的关系,具体中介者通常会保存所有组件的引用并对其进行管理,必要时候甚至会对它们的生命周期进行管理。
  • 各个组件相互之间并不知道其他组件的情况。如果组件内发生了重要事件,它只能通知中介者,中介者收到通知后首先要可以确认发送者,在确认需要触发的组件。对于各个组件来说中介者看上去完全就是一个黑箱,发送者不知道也不需要知道最终谁来处理自己的请求,接受者也不知道谁发出的请求。

3.代码示例

假设我们现在有这么一种场景。我们需要做一个简化的数据同步方案。目前共有三种数据库 MysqlRedisElasticsearch。其中 mysql作为主数据库。具体需求如下:

  • mysql 需要增加一条数据时,需要同步到另外两个数据库,同时本身还提供数据查询的功能。
  • redis 需要增加一条数据时,不需要同步到任何数据库。同时本身提供数据缓存的功能。
  • Elasticsearch需要增加一条数据时,需要同步到 mysql数据库中,同时本身提供数据统计功能。

我们先来实现一种我们正常逻辑下写的代码,各数据源维护各自的同步作业:

首先抽象数据库:

package com.gs.designmodel.mediator.base;

/**
 * @author: Gaos
 * @Date: 2023-08-21 16:47
 *
 * 抽象数据库
 **/
public abstract class AbstractDatabase {

    public abstract void sync(String data);

    public abstract void addData(String data);
}

具体数据库--Mysql:

package com.gs.designmodel.mediator.base;

import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: Gaos
 * @Date: 2023-08-21 16:50
 *
 * 具体数据库--mysql 需要维护同步到redis、es的任务
 **/
public class MysqlDatabase extends AbstractDatabase{

    private List<String> dataset = new ArrayList<>();

    @Setter
    private RedisDatabase redisDatabase;

    @Setter
    private EsDatabase esDatabase;

    /**
     * 需要维护同步到Es、redis的数据同步任务
     * @param data
     */
    @Override
    public void sync(String data) {
      addData(data);
      redisDatabase.addData(data);
      esDatabase.addData(data);
    }

    @Override
    public void addData(String data) {
        System.out.println("Mysql 添加数据" + data);
        this.dataset.add(data);
    }

    public void select() {
        System.out.println("Mysql 查询数据" + this.dataset.toString());
    }
}

具体数据库--Redis:

package com.gs.designmodel.mediator.base;

import java.util.LinkedList;
import java.util.List;

/**
 * @author: Gaos
 * @Date: 2023-08-21 16:56
 **/
public class RedisDatabase extends AbstractDatabase{

    private List<String> dataset = new LinkedList<>();

    /**
     * 不需要同步到其他数据库
     * @param data
     */
    @Override
    public void sync(String data) {
        addData(data);
    }

    @Override
    public void addData(String data) {
        System.out.println("Redis 添加数据 " + data);
        this.dataset.add(data);
    }

    public void cache() {
        System.out.println("Redis 缓存的数据" + this.dataset.toString());
    }
}

具体数据库--Elasticsearch:

package com.gs.designmodel.mediator.base;

import lombok.Setter;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: Gaos
 * @Date: 2023-08-21 16:59
 *
 * Es数据库
 **/
public class EsDatabase extends AbstractDatabase{

    private List<String> dataset = new CopyOnWriteArrayList<>();

    @Setter
    private MysqlDatabase mysqlDatabase;

    /**
     * 需要维护同步到mysql的同步任务
     * @param data
     */
    @Override
    public void sync(String data) {
        addData(data);
        this.mysqlDatabase.addData(data);
    }

    @Override
    public void addData(String data) {
        System.out.println("Es 添加数据" + data);
        this.dataset.add(data);
    }

    public void count() {
        System.out.println("Es统计目前共有" + this.dataset.size() + "条数据, 数据:" + this.dataset);
    }
}

测试类 分别往数据库春加入数据查看同步效果

package com.gs.designmodel.mediator.base;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:04
 **/
public class Test {
    public static void main(String[] args) {
        MysqlDatabase mysqlDatabase = new MysqlDatabase();
        RedisDatabase redisDatabase = new RedisDatabase();
        EsDatabase esDatabase = new EsDatabase();

        mysqlDatabase.setRedisDatabase(redisDatabase);
        mysqlDatabase.setEsDatabase(esDatabase);
        esDatabase.setMysqlDatabase(mysqlDatabase);

        System.out.println("----mysql 添加数据 mysqlA 需要同步到另外两个数据库中----");
        mysqlDatabase.sync("mysqlA");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("----Redis 添加数据 redisB 无须同步到其他数据库----");
        redisDatabase.sync("redisB");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("----Es 添加数据 esC 需要同步到Mysql中");
        esDatabase.sync("esC");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();
    }
}

输出结果:

----mysql 添加数据 mysqlA 需要同步到另外两个数据库中----
Mysql 添加数据mysqlA
Redis 添加数据 mysqlA
Es 添加数据mysqlA
Mysql 查询数据[mysqlA]
Redis 缓存的数据[mysqlA]
Es统计目前共有1条数据, 数据:[mysqlA]
----Redis 添加数据 redisB 无须同步到其他数据库----
Redis 添加数据 redisB
Mysql 查询数据[mysqlA]
Redis 缓存的数据[mysqlA, redisB]
Es统计目前共有1条数据, 数据:[mysqlA]
----Es 添加数据 esC 需要同步到Mysql中
Es 添加数据esC
Mysql 添加数据esC
Mysql 查询数据[mysqlA, esC]
Redis 缓存的数据[mysqlA, redisB]
Es统计目前共有2条数据, 数据:[mysqlA, esC]

这种代码结构虽然已经实现了我们的需求,但是缺陷也很明显:

  • 系统结构复杂且耦合度高:需要维护相关的对象引用,调用它的方法。
  • 组件的可重用性差:每个数据源和目标端有很强的关联性,如果没有目标端的支持很难被重用。
  • 系统的可拓展性差:如果要增加删除、修改一个数据库,或者更改一下需求规则,那么多个类的源代码都需要修改。

下面我们用中介者模式来编写这个需求代码。

首先还是抽象数据库类(相当于抽象组件类):

package com.gs.designmodel.mediator.pro;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:12
 *
 * 抽象数据库类(同事类),维护一个中介者对象的引用
 **/
public abstract class AbstractDatabase {

    public static final String MYSQL = "mysql";

    public static final String REDIS = "redis";

    public static final String ELASTICSEARCH = "elasticsearch";

    protected AbstractMediator mediator;

    public AbstractDatabase(AbstractMediator mediator) {
        this.mediator = mediator;
    }

    public abstract void addData(String data);

    public abstract void sync(String data);
}

Mysql数据库(具体组件类):

package com.gs.designmodel.mediator.pro;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:21
 *
 * 具体同事类->mysql
 **/
public class MysqlDatabase extends AbstractDatabase{

    private List<String> dataset = new ArrayList<>();

    public MysqlDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("Mysql 添加数据" + data);
        this.dataset.add(data);
    }

    /**
     * 数据同步作业交给中介者管理,这里无须关心
     * @param data
     */
    @Override
    public void sync(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.MYSQL, data);
    }

    public void select() {
        System.out.println("Mysql 查询数据" + this.dataset.toString());
    }
}

Redis数据库(具体组件类):

package com.gs.designmodel.mediator.pro;

import java.util.LinkedList;
import java.util.List;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:28
 *
 * 具体同事类->Redis
 **/
public class RedisDatabase extends AbstractDatabase{

    private List<String> dataset = new LinkedList<>();

    public RedisDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("Redis 添加数据" + data);
        this.dataset.add(data);
    }

    /**
     * 数据同步作业交给中介者管理,这里无须关心
     * @param data
     */
    @Override
    public void sync(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.REDIS, data);
    }

    public void cache() {
        System.out.println("Redis 缓存的数据" + this.dataset.toString());
    }
}

Elasticsearch数据库(具体组件类)

package com.gs.designmodel.mediator.pro;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:31
 **/
public class EsDatabase extends AbstractDatabase{

    private List<String> dataset = new CopyOnWriteArrayList<>();


    public EsDatabase(AbstractMediator mediator) {
        super(mediator);
    }

    @Override
    public void addData(String data) {
        System.out.println("Es 添加数据" + data);
        this.dataset.add(data);
    }

    /**
     * 数据同步作业交给中介者管理,这里无须关心
     * @param data
     */
    @Override
    public void sync(String data) {
        addData(data);
        this.mediator.sync(AbstractDatabase.ELASTICSEARCH, data);
    }

    public void count() {
        System.out.println("Es统计目前共有" + this.dataset.size() + "条数据, 数据:" + this.dataset);
    }
}

抽象中介者:

package com.gs.designmodel.mediator.pro;

import lombok.Data;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:19
 **/
@Data
public abstract class AbstractMediator {

    protected MysqlDatabase mysqlDatabase;

    protected RedisDatabase redisDatabase;

    protected EsDatabase esDatabase;

    public abstract void sync(String databaseName, String data);
}

具体中介者:

package com.gs.designmodel.mediator.pro;

/**
 * @author: Gaos
 * @Date: 2023-08-21 17:34
 **/
public class SyncMediator extends AbstractMediator{

    @Override
    public void sync(String databaseName, String data) {
        if(AbstractDatabase.MYSQL.equals(databaseName)) {
            // mysql需要同步到 redis和es
            this.redisDatabase.addData(data);
            this.esDatabase.addData(data);
        }else if(AbstractDatabase.REDIS.equals(databaseName)) {
            // 无须同步
        }else if(AbstractDatabase.ELASTICSEARCH.equals(databaseName)) {
            // es需要同步到 mysql
            this.mysqlDatabase.addData(data);
        }
    }
}

测试类:

package com.gs.designmodel.mediator.pro;


/**
 * @author: Gaos
 * @Date: 2023-08-21 17:36
 **/
public class Test {

    public static void main(String[] args) {
        SyncMediator syncMediator = new SyncMediator();
        MysqlDatabase mysqlDatabase = new MysqlDatabase(syncMediator);
        RedisDatabase redisDatabase = new RedisDatabase(syncMediator);
        EsDatabase esDatabase = new EsDatabase(syncMediator);

        syncMediator.setMysqlDatabase(mysqlDatabase);
        syncMediator.setRedisDatabase(redisDatabase);
        syncMediator.setEsDatabase(esDatabase);

        System.out.println("----mysql 添加数据 mysqlA 需要同步到另外两个数据库中----");
        mysqlDatabase.sync("mysqlA");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("----Redis 添加数据 redisB 无须同步到其他数据库----");
        redisDatabase.sync("redisB");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();

        System.out.println("----Es 添加数据 esC 需要同步到Mysql中");
        esDatabase.sync("esC");

        mysqlDatabase.select();
        redisDatabase.cache();
        esDatabase.count();
    }
}

结果:

----mysql 添加数据 mysqlA 需要同步到另外两个数据库中----
Mysql 添加数据mysqlA
Redis 添加数据mysqlA
Es 添加数据mysqlA
Mysql 查询数据[mysqlA]
Redis 缓存的数据[mysqlA]
Es统计目前共有1条数据, 数据:[mysqlA]
----Redis 添加数据 redisB 无须同步到其他数据库----
Redis 添加数据redisB
Mysql 查询数据[mysqlA]
Redis 缓存的数据[mysqlA, redisB]
Es统计目前共有1条数据, 数据:[mysqlA]
----Es 添加数据 esC 需要同步到Mysql中
Es 添加数据esC
Mysql 添加数据esC
Mysql 查询数据[mysqlA, esC]
Redis 缓存的数据[mysqlA, redisB]
Es统计目前共有2条数据, 数据:[mysqlA, esC]

可以看到结果与预期的一致。

4.总结

当一些对象和其他对象紧密耦合以至于难以对其进行修改时、当组件因过于依赖其他组件而无法复用时、为了在不同情景下复用一些基本行为导致你被迫创建大量组件子类时,你可以使用中介者模式。

但是它的缺点时经过了一段时间的演变后,中介者对象可能会演化成为上帝对象(了解过多或者复杂过多的对象),将所有组件交互的复杂逻辑都放到了中介者类中,反而使中介者类变成了最复杂的难以修改。

责任链模式、命令模式、中介者模式、观察者模式都用于处理请求发送者和接受者之间不同的连接方式:

  • 责任链按照顺序将请求动态传递给一系列的潜在接收者,直到其中一名接受者对请求进行处理。
  • 命令模式在发送者和请求者之间建立单向链接。
  • 中介者清除了发送者和请求者之间的直接连接,强迫它们通过一个中介对象进行间接沟通。
  • 观察者允许接受者动态的订阅或取消接受请求。

参考文章:

refactoringguru.cn/design-patt…

juejin.cn/post/684490…

希望这篇文章对大家有所帮助,您的赞和收藏是对我最大的支持和认可!