中介者模式:让对象间的“多对多”变成“一对多”

说实话,中介者模式是我在实际项目中用得比较多的模式之一。为什么?因为只要系统里对象一多,它们之间的通信就会变得混乱。你想想看,如果每个对象都要知道其他对象的存在,那代码维护起来简直是一场噩梦。

中介者模式的核心思想其实很简单:用一个中介对象来封装一组对象的交互。这些对象不再直接相互引用,而是通过中介者来通信。说白了,就是把“多对多”的关系,简化成“一对多”的关系。

核心定义:中介者模式(Mediator Pattern)定义了一个中介对象来封装一组对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互。

为什么需要中介者?

我在一个即时通讯项目里遇到过这样的问题:用户、群组、文件传输、通知服务……这些模块之间互相调用,代码里到处都是引用。后来加一个新功能,改了三个文件才搞定。我当时就想,这肯定得重构。

中介者模式正好解决这类问题。它把对象间的网状结构,变成了星型结构。每个对象只跟中介者打交道,中介者负责协调它们之间的交互。

嗯,这里要注意:中介者模式不是万能的。它适合那些对象间交互复杂、但交互逻辑相对稳定的场景。如果交互逻辑经常变,那中介者本身就会变成一个“上帝对象”,反而更难维护。

模式的结构

中介者模式有四个核心角色:

  • 抽象中介者(Mediator):定义通信接口,声明同事对象之间交互的方法。
  • 具体中介者(ConcreteMediator):实现中介者接口,协调各个同事对象的交互。它需要知道所有同事对象。
  • 抽象同事类(Colleague):定义同事对象的公共接口,持有中介者的引用。
  • 具体同事类(ConcreteColleague):实现自己的业务逻辑,当需要与其他同事通信时,通过中介者来完成。

我的习惯:在设计同事类时,我会让它们只关注自己的业务逻辑。通信的事情,全部交给中介者。这样每个同事类的职责就非常清晰,测试起来也方便。

代码示例:聊天室

聊天室是中介者模式最经典的例子。用户不直接给其他用户发消息,而是通过聊天室这个中介者来转发。

先看抽象中介者和抽象同事类:

// 抽象中介者
public abstract class ChatMediator {
    public abstract void sendMessage(String message, User user);
    public abstract void addUser(User user);
}

// 抽象同事类
public abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    
    public abstract void send(String message);
    public abstract void receive(String message);
    
    public String getName() {
        return name;
    }
}

再看具体实现:

// 具体中介者
public class ChatRoom extends ChatMediator {
    private List<User> users = new ArrayList<>();
    
    @Override
    public void addUser(User user) {
        users.add(user);
        System.out.println(user.getName() + " 加入了聊天室");
    }
    
    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            // 消息不发送给自己
            if (user != sender) {
                user.receive(sender.getName() + ": " + message);
            }
        }
    }
}

// 具体同事类
public class ChatUser extends User {
    public ChatUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    
    @Override
    public void send(String message) {
        System.out.println(this.name + " 发送消息: " + message);
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message) {
        System.out.println(this.name + " 收到消息: " + message);
    }
}

客户端使用:

public class Client {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom();
        
        User alice = new ChatUser(chatRoom, "Alice");
        User bob = new ChatUser(chatRoom, "Bob");
        User charlie = new ChatUser(chatRoom, "Charlie");
        
        chatRoom.addUser(alice);
        chatRoom.addUser(bob);
        chatRoom.addUser(charlie);
        
        alice.send("大家好!");
        bob.send("嗨,Alice!");
    }
}

运行结果:

Alice 加入了聊天室
Bob 加入了聊天室
Charlie 加入了聊天室
Alice 发送消息: 大家好!
Bob 收到消息: Alice: 大家好!
Charlie 收到消息: Alice: 大家好!
Bob 发送消息: 嗨,Alice!
Alice 收到消息: Bob: 嗨,Alice!
Charlie 收到消息: Bob: 嗨,Alice!

中介者模式与MVC中的Controller

说到中介者模式,我不得不提MVC架构。你仔细想想,MVC里的Controller其实就是一个中介者。

在MVC中:

  • View 负责展示数据,它不直接操作Model。
  • Model 负责业务数据和逻辑,它不关心谁在展示它。
  • Controller 负责协调View和Model的交互。

用户点击按钮 -> View通知Controller -> Controller更新Model -> Model变化后通知View更新。你看,这不就是中介者模式吗?Controller就是那个中介者,View和Model就是同事类。

我曾经踩过的坑:在一个项目中,我把太多逻辑塞进了Controller里。结果Controller变得臃肿不堪,什么都要管。后来我意识到,Controller应该只负责协调,具体的业务逻辑应该放在Model或者Service层。记住:中介者只负责“传话”,不负责“做事”。

中介者模式的优缺点

优点 缺点
降低了同事类之间的耦合度 中介者可能变得复杂庞大
将对象间的交互集中管理 中介者出问题会影响整个系统
符合迪米特法则(最少知道原则) 增加了系统的复杂度
便于扩展新的同事类 交互逻辑变更时需要修改中介者

什么时候用中介者模式?

我个人总结了几条经验:

  1. 对象间引用关系复杂:当多个对象相互引用,形成网状结构时,考虑引入中介者。
  2. 想复用对象:如果对象需要在不修改代码的情况下被复用到其他场景,中介者模式能帮你解耦。
  3. 交互逻辑需要集中管理:当对象间的交互逻辑经常变化,或者需要统一控制时,中介者是个好选择。

避坑指南:我曾经在一个项目中过度使用中介者模式,把所有的对象交互都塞进了一个中介者里。结果那个中介者变成了“上帝对象”,维护起来非常痛苦。后来我学会了:如果交互逻辑可以分组,就创建多个中介者,每个中介者负责一组相关的交互。

中介者模式的核心逻辑图

下面这张图展示了中介者模式的核心结构。你看,所有的同事类都只跟中介者通信,彼此之间没有直接联系。

中介者模式结构图 中介者 Mediator 同事类1 Colleague1 同事类2 Colleague2 同事类3 Colleague3 所有同事类只与中介者通信 彼此之间没有直接引用 通信 网状结构 → 星型结构

实际项目中的应用

我记得在一个电商系统中,订单、库存、支付、物流这几个模块之间交互非常频繁。一开始它们直接调用对方的方法,代码耦合严重。后来我用中介者模式重构了这部分逻辑,引入了一个订单处理中心(OrderProcessingCenter)作为中介者。

重构之后,每个模块只跟订单处理中心通信。新增一个优惠券模块时,我只需要修改订单处理中心,其他模块完全不用动。这就是中介者模式的价值所在。

总结一下:中介者模式的核心是“解耦”。它让对象间的交互变得清晰可控。但要注意,不要滥用。如果对象间的交互很简单,直接用引用反而更直接。模式是工具,不是目的。

公众号:蓝海资料掘金营,微信deep3321