← 返回题目列表

如何手写一个中介者模式?Java 代码怎么实现?

高频 中等 第 3 / 25 题 更新于 2026/07/28
中介者模式Java代码实现聊天室设计模式

简化版

手写中介者模式的关键是定义一个中介者接口,让同事对象只调用中介者,不直接调用彼此。具体中介者保存同事对象集合,并负责把请求转发或协调给合适的对象。

详细版

以聊天室为例:

  • 用户之间不直接互相发送消息;
  • 用户只把消息交给聊天室;
  • 聊天室决定把消息发给哪些用户。

核心代码结构:

interface ChatRoom {
    void sendMessage(User from, String message);
}
class User {
    private final String name;
    private final ChatRoom chatRoom;

    public User(String name, ChatRoom chatRoom) {
        this.name = name;
        this.chatRoom = chatRoom;
    }

    public void send(String message) {
        chatRoom.sendMessage(this, message);
    }

    public void receive(String message) {
        System.out.println(name + " received: " + message);
    }

    public String getName() {
        return name;
    }
}

用户只依赖聊天室,不依赖其他用户。聊天室负责统一转发消息,这就是中介者模式。

完整版教学

一、先明确需求

假设我们要写一个多人聊天室。

最直接的写法是让用户对象保存其他用户列表:

class User {
    private List<User> friends;
}

但这样会带来几个问题:

  • 每个用户都要知道其他用户;
  • 添加、删除用户时,很多对象都要同步变化;
  • 群聊、私聊、禁言、频道隔离等规则会散落到用户对象里;
  • 用户对象越来越重。

更好的做法是引入聊天室作为中介者。用户只管发送和接收,消息分发规则交给聊天室。

二、定义中介者接口

interface ChatRoom {
    void register(User user);

    void sendMessage(User from, String message);
}

这个接口表达两件事:

  1. 用户可以加入聊天室;
  2. 用户可以把消息交给聊天室发送。

用户对象不需要知道聊天室内部如何保存成员,也不需要知道消息发给谁。

三、实现具体中介者

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

class GroupChatRoom implements ChatRoom {
    private final List<User> users = new ArrayList<>();

    @Override
    public void register(User user) {
        if (!users.contains(user)) {
            users.add(user);
        }
    }

    @Override
    public void sendMessage(User from, String message) {
        for (User user : users) {
            if (user != from) {
                user.receive(from.getName() + ": " + message);
            }
        }
    }
}

这里的 GroupChatRoom 就是具体中介者。

它保存所有用户,并负责消息分发。以后如果要加黑名单、禁言、敏感词过滤,也可以在聊天室协调层扩展,而不是让用户对象互相判断。

四、定义同事对象

class User {
    private final String name;
    private final ChatRoom chatRoom;

    public User(String name, ChatRoom chatRoom) {
        this.name = name;
        this.chatRoom = chatRoom;
        this.chatRoom.register(this);
    }

    public void send(String message) {
        chatRoom.sendMessage(this, message);
    }

    public void receive(String message) {
        System.out.println(name + " received -> " + message);
    }

    public String getName() {
        return name;
    }
}

用户对象只做两件事:

  • send():把消息交给中介者;
  • receive():接收中介者转发的消息。

它不需要保存其他用户引用。

五、客户端使用

public class MediatorDemo {
    public static void main(String[] args) {
        ChatRoom room = new GroupChatRoom();

        User alice = new User("Alice", room);
        User bob = new User("Bob", room);
        User cindy = new User("Cindy", room);

        alice.send("大家好");
        bob.send("欢迎 Alice");
    }
}

调用过程是:

Alice.send()

ChatRoom.sendMessage()

GroupChatRoom 遍历其他用户

Bob.receive() / Cindy.receive()

用户之间没有互相调用,但消息仍能正常流转。

六、这个例子体现了哪些设计点

  1. 依赖方向变简单:用户依赖聊天室,而不是依赖所有其他用户;
  2. 协作逻辑集中:群发规则在聊天室里;
  3. 扩展更容易:加私聊、禁言、频道,可以在聊天室层增加规则;
  4. 同事对象更纯粹:用户对象只保留自身行为。

不过也要注意,如果聊天室逻辑越来越复杂,应继续拆分,比如把权限、过滤、路由拆成独立组件,再由中介者组合它们。

七、常见误区与追问

实现时要让同事只报告意图,不直接操纵其他同事。表单有3个控件时,复选框变化通知 Mediator,由它决定文本框是否启用、按钮是否可点;复选框不应持有按钮引用。中介者的处理方法还要防止程序性更新再次触发同一事件形成递归。

检查维度判定依据
Colleague维护自身状态并通知事件
Mediator读取必要状态并协调其他同事
Checkbox -> Mediator -> TextBox + Button

易错点:中介者调用同事更新时,要区分用户事件和程序性同步。

  • 误区:ConcreteMediator 应继承所有组件。 它只需持有或注册同事引用,不是组件子类集合。
  • 追问:Mediator 接口用 notify 还是类型方法? 事件少可用明确方法,事件多时可用类型化事件,避免字符串分支。
  • 误区:同事不能直接改变自身状态。 自身不变量仍由同事管理,跨对象影响才通知中介者。
  • 追问:如何防止事件重入? 使用更新来源、状态比较或重入保护,避免无变化也再次通知。
  • 追问:测试应如何分层? 单测同事自身行为,再测试中介者在3个组件间的协调结果。

八、加强记忆

手写中介者模式时,抓住“同事不互调,中介来协调”。接口定义通信入口,具体中介者保存参与对象并执行协作规则,同事对象只通知中介者和接收结果。聊天室例子最典型:用户不直接认识其他用户,而是把消息交给聊天室统一分发。