【软考设计模式】中介者模式:网状交互的解耦与集中调度精讲

系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 5 讲

考察分值:上午题 1-2 分,下午题常作为类图识别或代码填空出现

难度等级:⭐⭐⭐☆☆(结构清晰,Mediator 与 Colleague 的关联关系是填空重点)


一、考纲定位与模式定义
1.1 考纲要求

中介者模式在软考中属于 行为型模式 的基础内容。考察形式包括:

  • 上午选择题:判断场景描述所属模式;识别中介者模式与观察者模式、外观模式的区别;判断 Mediator、ConcreteMediator、Colleague、ConcreteColleague 四个角色;识别 "多对多交互解耦为一对多" 的设计意图

  • 下午设计题 :补全 Colleague 中持有 Mediator 引用的代码;补全 ConcreteMediator 中协调多个 Colleague 交互的逻辑;补全 Mediator 接口中定义通信方法(如 sendMessagenotify)的代码;识别类图中 Mediator 与 Colleague 的关联关系

1.2 模式定义

中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

核心意图 :当一个系统中 多个对象之间互相引用 ,形成复杂的 网状关系(N 个对象两两交互,最多需要 N×(N-1)/2 条关联线)时,如果每个对象都直接持有其他对象的引用,会导致:

  1. 系统耦合度极高,一个对象改动会影响所有相关对象

  2. 新增对象时需要修改大量现有类

  3. 对象之间的交互逻辑散落在各个类中,难以维护

中介者模式引入一个 "中介者" 对象,所有对象不再直接互相引用,而是 统一通过中介者通信 。N 个对象的网状交互被解耦为 N 个对象与 1 个中介者的星型交互,对象之间从 "多对多" 变成 "一对多"。

通俗理解

你搬进一个新小区,需要和邻居们互相认识。如果没有物业(中介者),你要分别记住所有邻居的电话(张三、李四、王五......),想通知大家停水,你得给每个人打电话。每搬来一个新邻居,你都要更新通讯录,还要告诉他所有人的电话------这就是 网状关系,复杂且难以维护。

有了物业(中介者)之后,你不需要知道任何邻居的电话,只需要知道物业的电话。想通知停水?给物业打个电话,物业帮你群发。新邻居搬来?只需要告诉物业一声,物业负责对接所有人。所有人的交互都通过物业集中处理,这就是 星型关系,简单且易于扩展。


二、UML 类图与角色划分
复制代码
┌───────────────────────────────────────────────────────────────┐
│                      Client                                   │
│  + main()                                                     │
│    ├── ConcreteMediator mediator = new ConcreteMediator()     │
│    ├── ConcreteColleagueA a = new ConcreteColleagueA(mediator)│
│    ├── ConcreteColleagueB b = new ConcreteColleagueB(mediator)│
│    ├── mediator.registerColleague(a)                          │
│    ├── mediator.registerColleague(b)                          │
│    └── a.send("你好,B")  // A 通过中介者发给 B                  │
└───────────────────────┬───────────────────────────────────────┘
                        │ uses
                        ▼
┌─────────────────────────────────────────────────────────┐
│                  <<interface>>                          │
│                    Mediator                             │
│  + sendMessage(String msg, Colleague sender)            │
│  + registerColleague(Colleague c)                       │
└───────────────────────┬─────────────────────────────────┘
                        │ implements
                        ▼
┌─────────────────────────────────────────────────────────┐
│               ConcreteMediator                          │
│  - colleagues: List<Colleague>                          │
│  + sendMessage(msg, sender) {                           │
│      for (Colleague c : colleagues) {                   │
│        if (c != sender) c.receive(msg);  // 转发给其他人  │
│      }                                                  │
│    }                                                    │
│  + registerColleague(c) { colleagues.add(c); }          │
└───────────────────────┬─────────────────────────────────┘
           ▲            │ knows all
           │            ▼
           │   ┌────────────────────────┐
           │   │   <<interface>>        │
           │   │     Colleague          │
           │   │ + send(String msg)     │
           │   │ + receive(String msg)  │
           │   └────────────────────────┘
           │            ▲
           │            │ implements
           └────────────┤
                        │
           ┌────────────┴──────────────────────┐
           ▼                                   ▼
┌────────────────────────────────────┐   ┌────────────────────────────────────┐
│ ConcreteColleagueA                 │   │ ConcreteColleagueB                 │
│ - mediator: Mediator               │   │ - mediator: Mediator               │
│ + send(msg) {                      │   │ + send(msg) {                      │
│     mediator.sendMessage(msg, this)│   │     mediator.sendMessage(msg, this)│
│   }                                │   │   }                                │
│ + receive(msg) {                   │   │ + receive(msg) {                   │
│     // 处理消息                     │   │     // 处理消息                      │
│   }                                │   │   }                                │
└────────────────────────────────────┘   └────────────────────────────────────┘
角色 职责 软考填空关键词
Mediator(抽象中介者) 定义与同事对象通信的接口,声明用于协调同事对象交互的方法(如 sendMessagenotify interface Mediator + void sendMessage(String msg, Colleague sender)
ConcreteMediator(具体中介者) 实现 Mediator 接口,持有所有 ConcreteColleague 的引用(通常用 List 或 Map),封装具体的协调逻辑,负责消息的转发和过滤 implements Mediator + List<Colleague> colleagues + 转发逻辑
Colleague(抽象同事) 定义与中介者通信的接口,声明发送和接收消息的方法 interface Colleague + void send(String msg) + void receive(String msg)
ConcreteColleague(具体同事) 实现 Colleague 接口,持有 Mediator 的引用(通常通过构造器注入),发送消息时委托给 Mediator,接收消息时处理业务逻辑 implements Colleague + private Mediator mediator + mediator.sendMessage(msg, this)
Client(客户端) 创建 ConcreteMediator、创建 ConcreteColleague 并注入 Mediator、注册同事到中介者、触发同事发送消息 new ConcreteMediator() + new ConcreteColleagueX(mediator) + registerColleague()

类图识别要点 :软考类图中,中介者模式的核心特征是 一个 Mediator 类持有多个 Colleague 的引用(通常用 List/Map),且 Colleague 也持有 Mediator 的引用(双向关联)。如果类图里有 "一个中心类管理多个对象之间的通信,对象之间不直接相连" → 中介者模式。


三、场景一:聊天室系统(最经典的中介者模式)

业务背景:聊天室中有多个用户(User),用户之间需要互相发送消息。如果每个用户都直接持有其他用户的引用,新增用户时需要修改所有现有用户类。使用中介者模式,引入聊天室(ChatRoom)作为中介者,所有用户通过聊天室转发消息。

说明:这是中介者模式最经典的例子,也是软考下午题最爱考的 "多对象交互解耦" 场景。

3.1 代码实现
java 复制代码
// Mediator:聊天室中介者接口
public interface ChatRoom {
    void sendMessage(String message, User sender);
    void registerUser(User user);
}

// ConcreteMediator:具体聊天室
public class ConcreteChatRoom implements ChatRoom {
    private List<User> users = new ArrayList<>();

    @Override
    public void registerUser(User user) {
        users.add(user);
        System.out.println("【系统】" + user.getName() + " 加入聊天室");
    }

    @Override
    public void sendMessage(String message, User sender) {
        System.out.println("【中介者】收到 " + sender.getName() + " 的消息,开始转发...");
        for (User user : users) {
            // 不转发给发送者自己
            if (user != sender) {
                user.receive(message, sender);
            }
        }
    }
}

// Colleague:用户抽象类
public abstract class User {
    protected String name;
    protected ChatRoom chatRoom;

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

    public String getName() {
        return name;
    }

    // 发送消息:委托给中介者
    public void send(String message) {
        System.out.println("【用户】" + name + " 发送消息: " + message);
        chatRoom.sendMessage(message, this);
    }

    // 接收消息:由中介者调用
    public abstract void receive(String message, User sender);
}

// ConcreteColleague:普通用户
public class NormalUser extends User {
    public NormalUser(String name, ChatRoom chatRoom) {
        super(name, chatRoom);
    }

    @Override
    public void receive(String message, User sender) {
        System.out.println("【普通用户】" + name + " 收到来自 " + sender.getName() + " 的消息: " + message);
    }
}

// ConcreteColleague:VIP 用户
public class VipUser extends User {
    public VipUser(String name, ChatRoom chatRoom) {
        super(name, chatRoom);
    }

    @Override
    public void receive(String message, User sender) {
        System.out.println("【VIP用户】" + name + " 收到来自 " + sender.getName() + " 的消息: " + message);
    }
}

// Client:客户端
public class Client {
    public static void main(String[] args) {
        // 创建中介者
        ChatRoom chatRoom = new ConcreteChatRoom();

        // 创建同事,注入中介者
        User zhangsan = new NormalUser("张三", chatRoom);
        User lisi = new NormalUser("李四", chatRoom);
        User wangwu = new VipUser("王五", chatRoom);

        // 注册到中介者
        chatRoom.registerUser(zhangsan);
        chatRoom.registerUser(lisi);
        chatRoom.registerUser(wangwu);

        System.out.println("---");

        // 张三发消息,中介者负责转发给李四和王五
        zhangsan.send("大家好!");
        System.out.println("---");

        // 王五发消息,中介者负责转发给张三和李四
        wangwu.send("VIP 用户报到!");
    }
}

关键体会

  • User 类(Colleague)只持有 ChatRoom(Mediator)的引用,不持有其他任何 User 的引用 。发送消息时,通过 chatRoom.sendMessage(message, this) 委托给中介者。

  • ConcreteChatRoom(ConcreteMediator)持有所有 User 的列表,负责 消息的转发和过滤(不转发给发送者自己)。所有交互逻辑集中在中介者中。

  • 新增一个用户(如赵六),只需创建 new NormalUser("赵六", chatRoom) 并注册到中介者,不需要修改 张三、李四、王五的代码。这符合 开闭原则

  • 如果没有中介者,3 个用户需要 3×2/2 = 3 条关联线;4 个用户需要 6 条;N 个用户需要 N×(N-1)/2 条。有了中介者,N 个用户只需要 N 条关联线(每个用户连到中介者),复杂度从 O(N²) 降到 O(N)。


四、场景二:航空管制系统(实际开发场景)

业务背景:机场有多个飞机(Aircraft),飞机之间需要协调起降(如 A 飞机要降落,需要通知 B 飞机避让、C 飞机等待)。如果每架飞机直接与其他飞机通信,系统极其复杂。引入航空管制塔(ControlTower)作为中介者,所有飞机通过管制塔协调。

说明:这是实际开发中中介者模式的典型应用,也是软考下午题中 "集中调度协调" 场景的代表。

4.1 代码实现
java 复制代码
// Mediator:航空管制塔接口
public interface ControlTower {
    void requestLanding(Aircraft aircraft);
    void requestTakeoff(Aircraft aircraft);
    void registerAircraft(Aircraft aircraft);
}

// ConcreteMediator:具体管制塔
public class AirportControlTower implements ControlTower {
    private List<Aircraft> aircrafts = new ArrayList<>();
    private boolean runwayAvailable = true;

    @Override
    public void registerAircraft(Aircraft aircraft) {
        aircrafts.add(aircraft);
        System.out.println("【管制塔】注册飞机: " + aircraft.getCallSign());
    }

    @Override
    public void requestLanding(Aircraft aircraft) {
        System.out.println("【管制塔】收到 " + aircraft.getCallSign() + " 的降落请求");
        if (runwayAvailable) {
            runwayAvailable = false;
            System.out.println("【管制塔】跑道空闲,允许 " + aircraft.getCallSign() + " 降落");
            aircraft.allowLanding();
            
            // 通知其他飞机等待
            for (Aircraft other : aircrafts) {
                if (other != aircraft) {
                    other.notifyWait("跑道被占用," + aircraft.getCallSign() + " 正在降落");
                }
            }
        } else {
            System.out.println("【管制塔】跑道繁忙," + aircraft.getCallSign() + " 请盘旋等待");
            aircraft.notifyWait("跑道繁忙,请等待");
        }
    }

    @Override
    public void requestTakeoff(Aircraft aircraft) {
        System.out.println("【管制塔】收到 " + aircraft.getCallSign() + " 的起飞请求");
        if (runwayAvailable) {
            runwayAvailable = false;
            System.out.println("【管制塔】跑道空闲,允许 " + aircraft.getCallSign() + " 起飞");
            aircraft.allowTakeoff();
        } else {
            aircraft.notifyWait("跑道繁忙,请等待");
        }
    }

    // 跑道释放(由飞机降落后/起飞后通知中介者)
    public void releaseRunway() {
        runwayAvailable = true;
        System.out.println("【管制塔】跑道已释放");
    }
}

// Colleague:飞机抽象类
public abstract class Aircraft {
    protected String callSign;
    protected ControlTower controlTower;

    public Aircraft(String callSign, ControlTower controlTower) {
        this.callSign = callSign;
        this.controlTower = controlTower;
    }

    public String getCallSign() {
        return callSign;
    }

    // 请求降落:委托给中介者
    public void requestLanding() {
        System.out.println("【飞机】" + callSign + " 请求降落");
        controlTower.requestLanding(this);
    }

    // 请求起飞:委托给中介者
    public void requestTakeoff() {
        System.out.println("【飞机】" + callSign + " 请求起飞");
        controlTower.requestTakeoff(this);
    }

    // 通知等待(由中介者调用)
    public abstract void notifyWait(String reason);

    // 允许降落(由中介者调用)
    public abstract void allowLanding();

    // 允许起飞(由中介者调用)
    public abstract void allowTakeoff();

    // 完成操作后通知中介者释放资源
    public void completeOperation() {
        System.out.println("【飞机】" + callSign + " 完成操作");
        if (controlTower instanceof AirportControlTower) {
            ((AirportControlTower) controlTower).releaseRunway();
        }
    }
}

// ConcreteColleague:客机
public class PassengerPlane extends Aircraft {
    public PassengerPlane(String callSign, ControlTower controlTower) {
        super(callSign, controlTower);
    }

    @Override
    public void notifyWait(String reason) {
        System.out.println("【客机】" + callSign + " 收到通知: " + reason);
    }

    @Override
    public void allowLanding() {
        System.out.println("【客机】" + callSign + " 开始降落...");
    }

    @Override
    public void allowTakeoff() {
        System.out.println("【客机】" + callSign + " 开始起飞...");
    }
}

// ConcreteColleague:货机
public class CargoPlane extends Aircraft {
    public CargoPlane(String callSign, ControlTower controlTower) {
        super(callSign, controlTower);
    }

    @Override
    public void notifyWait(String reason) {
        System.out.println("【货机】" + callSign + " 收到通知: " + reason);
    }

    @Override
    public void allowLanding() {
        System.out.println("【货机】" + callSign + " 开始降落(重载,需长跑道)...");
    }

    @Override
    public void allowTakeoff() {
        System.out.println("【货机】" + callSign + " 开始起飞(重载,需长跑道)...");
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        ControlTower tower = new AirportControlTower();

        Aircraft plane1 = new PassengerPlane("CA1234", tower);
        Aircraft plane2 = new PassengerPlane("MU5678", tower);
        Aircraft plane3 = new CargoPlane("CK9999", tower);

        tower.registerAircraft(plane1);
        tower.registerAircraft(plane2);
        tower.registerAircraft(plane3);

        System.out.println("---");

        // 客机1请求降落
        plane1.requestLanding();
        System.out.println("---");

        // 客机2请求降落(跑道被占用,需等待)
        plane2.requestLanding();
        System.out.println("---");

        // 客机1完成降落,释放跑道
        plane1.completeOperation();
        System.out.println("---");

        // 客机2再次请求降落(此时跑道空闲)
        plane2.requestLanding();
    }
}

五、场景三:UI 组件事件协调(对象交互解耦场景)

业务背景:一个表单页面包含多个组件:用户名输入框(TextBox)、密码输入框(PasswordBox)、登录按钮(Button)、提示标签(Label)。这些组件之间存在交互:①用户名或密码为空时,登录按钮禁用;②点击登录时,所有输入框清空;③输入错误时,提示标签显示错误信息。使用中介者模式将组件间的交互逻辑集中到表单中介者中。

说明:这是中介者模式在 UI 组件协调场景中的典型应用,也是软考上午题中 "组件间交互解耦" 场景的代表。

5.1 代码实现
java 复制代码
// Mediator:表单中介者接口
public interface FormMediator {
    void componentChanged(Component component);
    void login();
    void clearForm();
}

// ConcreteMediator:具体表单中介者
public class LoginFormMediator implements FormMediator {
    private TextBox usernameBox;
    private PasswordBox passwordBox;
    private Button loginButton;
    private Label messageLabel;

    // 设置组件(由客户端注入)
    public void setUsernameBox(TextBox usernameBox) {
        this.usernameBox = usernameBox;
    }

    public void setPasswordBox(PasswordBox passwordBox) {
        this.passwordBox = passwordBox;
    }

    public void setLoginButton(Button loginButton) {
        this.loginButton = loginButton;
    }

    public void setMessageLabel(Label messageLabel) {
        this.messageLabel = messageLabel;
    }

    @Override
    public void componentChanged(Component component) {
        // 集中处理组件变化逻辑
        if (component == usernameBox || component == passwordBox) {
            // 检查用户名和密码是否都不为空
            boolean canLogin = !usernameBox.getText().isEmpty() 
                && !passwordBox.getText().isEmpty();
            loginButton.setEnabled(canLogin);
            if (canLogin) {
                messageLabel.setText("可以登录");
            } else {
                messageLabel.setText("请输入用户名和密码");
            }
        }
    }

    @Override
    public void login() {
        String username = usernameBox.getText();
        String password = passwordBox.getText();
        
        // 模拟登录验证
        if ("admin".equals(username) && "123456".equals(password)) {
            messageLabel.setText("登录成功!");
            clearForm();
        } else {
            messageLabel.setText("用户名或密码错误");
            passwordBox.clear();
        }
    }

    @Override
    public void clearForm() {
        usernameBox.clear();
        passwordBox.clear();
        loginButton.setEnabled(false);
        messageLabel.setText("请输入用户名和密码");
    }
}

// Colleague:组件抽象类
public abstract class Component {
    protected FormMediator mediator;

    public void setMediator(FormMediator mediator) {
        this.mediator = mediator;
    }

    // 组件变化时通知中介者
    protected void changed() {
        if (mediator != null) {
            mediator.componentChanged(this);
        }
    }
}

// ConcreteColleague:文本输入框
public class TextBox extends Component {
    private String text = "";

    public void setText(String text) {
        this.text = text;
        changed();  // 内容变化,通知中介者
    }

    public String getText() {
        return text;
    }

    public void clear() {
        this.text = "";
        System.out.println("【文本框】已清空");
    }
}

// ConcreteColleague:密码输入框
public class PasswordBox extends Component {
    private String text = "";

    public void setText(String text) {
        this.text = text;
        changed();
    }

    public String getText() {
        return text;
    }

    public void clear() {
        this.text = "";
        System.out.println("【密码框】已清空");
    }
}

// ConcreteColleague:按钮
public class Button extends Component {
    private boolean enabled = false;
    private String label;

    public Button(String label) {
        this.label = label;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        System.out.println("【按钮】" + label + " " + (enabled ? "启用" : "禁用"));
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void click() {
        if (enabled && mediator != null) {
            System.out.println("【按钮】" + label + " 被点击");
            mediator.login();
        } else {
            System.out.println("【按钮】" + label + " 未启用,无法点击");
        }
    }
}

// ConcreteColleague:标签
public class Label extends Component {
    private String text = "";

    public void setText(String text) {
        this.text = text;
        System.out.println("【标签】显示: " + text);
    }

    public String getText() {
        return text;
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        // 创建中介者
        LoginFormMediator mediator = new LoginFormMediator();

        // 创建组件
        TextBox username = new TextBox();
        PasswordBox password = new PasswordBox();
        Button loginBtn = new Button("登录");
        Label msgLabel = new Label();

        // 设置中介者
        username.setMediator(mediator);
        password.setMediator(mediator);
        loginBtn.setMediator(mediator);
        msgLabel.setMediator(mediator);

        // 将组件注入中介者(双向绑定)
        mediator.setUsernameBox(username);
        mediator.setPasswordBox(password);
        mediator.setLoginButton(loginBtn);
        mediator.setMessageLabel(msgLabel);

        System.out.println("=== 用户输入用户名 ===");
        username.setText("admin");

        System.out.println("=== 用户输入密码 ===");
        password.setText("123456");

        System.out.println("=== 点击登录 ===");
        loginBtn.click();

        System.out.println("=== 尝试错误密码 ===");
        password.setText("wrong");
        loginBtn.click();
    }
}

六、三种场景对比与演进思路
维度 场景一:聊天室 场景二:航空管制 场景三:UI 组件协调
Mediator ChatRoom(聊天室接口) ControlTower(管制塔接口) FormMediator(表单中介者接口)
ConcreteMediator ConcreteChatRoom(转发消息) AirportControlTower(调度跑道) LoginFormMediator(协调组件状态)
Colleague User(用户抽象类) Aircraft(飞机抽象类) Component(组件抽象类)
ConcreteColleague NormalUserVipUser PassengerPlaneCargoPlane TextBoxPasswordBoxButtonLabel
交互方式 群发消息(一对多广播) 资源调度(请求-审批-通知) 状态联动(输入变化→按钮状态更新)
中介者持有 List<User> List<Aircraft> + runwayAvailable 各组件引用(直接注入)
同事持有 ChatRoom(构造器注入) ControlTower(构造器注入) FormMediator(setter 注入)
发送委托 chatRoom.sendMessage(msg, this) controlTower.requestLanding(this) mediator.componentChanged(this)
新增同事 创建 + 注册到中介者 创建 + 注册到中介者 创建 + 设置中介者 + 注入中介者
软考考法 下午大题(补全转发逻辑) 下午大题(补全调度逻辑) 上午选择题(识别组件解耦)

七、中介者模式 vs 观察者模式 vs 外观模式:核心对比
对比项 中介者模式 观察者模式 外观模式
模式分类 行为型 行为型 结构型
核心思想 封装多对象交互,将网状关系解耦为星型关系 定义对象间一对多依赖,状态变化时自动通知 为子系统提供统一接口,简化客户端使用
交互方向 多对多 通过中介者转接(双向) 一对多 广播(Subject → Observer,单向) 客户端 → 外观 → 子系统(单向封装)
对象关系 Colleague 之间 不直接引用,只引用 Mediator Observer 之间 无直接引用,只引用 Subject 子系统之间 可能互相引用,外观统一封装
关注点 对象之间的交互逻辑(行为协调) 状态变化的通知机制(事件订阅) 接口的简化与统一(封装复杂度)
客户端角色 创建 Mediator 和 Colleague,注册并触发交互 创建 Subject 和 Observer,注册订阅关系 只与 Facade 交互,不直接接触子系统
新增扩展 新增 Colleague:容易(注册到 Mediator);新增交互逻辑:修改 Mediator 新增 Observer:容易(注册到 Subject);新增 Subject:独立 新增子系统:修改 Facade;新增外观:独立
典型应用 聊天室、航空管制、MVC 控制器、UI 组件协调 事件监听、消息订阅、MVC 模型通知视图 复杂 API 封装、分层架构入口、遗留系统包装
软考判断 看到 "多个对象互相引用"、"网状关系解耦"、"集中调度" → 中介者 看到 "一对多通知"、"状态变化自动更新"、"订阅发布" → 观察者 看到 "简化接口"、"统一入口"、"封装子系统" → 外观

记忆口诀:中介者是 "物业管所有业主"(多对多解耦),观察者是 "公众号推送给订阅者"(一对多广播),外观是 "酒店前台包办一切"(统一入口)。


八、软考高频考点与易混淆辨析
8.1 高频考点
考点 内容
模式分类 行为型模式(GoF 23 正式成员)
核心四角色 Mediator(抽象中介者)、ConcreteMediator(具体中介者)、Colleague(抽象同事)、ConcreteColleague(具体同事)
核心思想 用一个中介对象封装一系列对象交互,使对象不需要显式相互引用,降低耦合
解耦效果 将对象间的 网状关系(N×(N-1)/2) 解耦为 星型关系(N)
双向关联 ConcreteMediator 持有所有 Colleague 引用;ConcreteColleague 持有 Mediator 引用
消息转发 ConcreteMediator 负责接收消息并转发给相关 Colleague(可过滤、可广播)
与观察者模式区别 中介者:多对多交互,双向协调;观察者:一对多通知,单向广播
与外观模式区别 中介者:行为型,关注对象间交互解耦;外观:结构型,关注接口简化封装
适用场景 ① 多个对象互相引用形成复杂网状关系 ② 对象间交互逻辑复杂且经常变化 ③ 需要集中控制交互逻辑
不适用场景 对象间交互简单(引入中介者反而增加复杂度);同事类数量极少(没必要)
8.2 易混淆辨析:中介者 vs 观察者 vs 外观
对比项 中介者模式 观察者模式 外观模式
UML 结构 Mediator ↔ Colleague 双向关联,Mediator 持有多个 Colleague Subject → Observer 单向关联,Subject 持有多个 Observer Facade → Subsystem 单向关联,Facade 封装多个 Subsystem
交互复杂度 解决 多对多 交互 解决 一对多 通知 解决 一对多 接口封装
核心方法 sendMessage()notify()(协调转发) attach()detach()notify()(订阅管理) simplifiedMethod()(简化调用)
客户端调用 直接与 Colleague 交互,Colleague 委托 Mediator 直接与 Subject 交互,Subject 自动通知 Observer 只与 Facade 交互,不接触 Subsystem
扩展方向 新增 Colleague:注册到 Mediator;新增交互逻辑:修改 Mediator 新增 Observer:注册到 Subject;新增 Subject:独立 新增子系统:修改 Facade;新增 Facade:独立
软考考法 代码填空(补全 Mediator 转发逻辑 + Colleague 委托调用) 代码填空(补全 Subject 的 notify + Observer 的 update) 代码填空(补全 Facade 的封装方法)

九、真题风格模拟与代码填空
模拟题 1(上午选择题)

以下关于中介者模式的叙述中,正确的是()。

A. 中介者模式属于结构型模式,主要用于简化复杂子系统的接口

B. 在中介者模式中,ConcreteColleague 之间可以直接引用并相互通信

C. 中介者模式用一个中介对象封装一系列对象交互,使各对象不需要显式相互引用,从而降低耦合

D. 中介者模式与观察者模式的核心区别在于:中介者模式是一对多通知,观察者模式是多对多交互协调

答案:C

解析

  • A 错误:中介者模式属于 行为型 模式,不是结构型。简化复杂子系统接口的是 外观模式

  • B 错误:中介者模式的核心就是 禁止 ConcreteColleague 之间直接引用。所有通信必须通过 Mediator 转发。如果直接引用,就失去了中介者的意义。

  • C 正确:这是中介者模式的核心定义(GoF 原书定义)。

  • D 错误:说反了。中介者模式是 多对多交互协调 ,观察者模式是 一对多通知


模拟题 2(下午代码填空 --- 补全 Mediator 转发逻辑和 Colleague 委托)

某系统使用中介者模式实现会议室预定系统。 MeetingRoom 是会议室(Colleague), MeetingMediator 是中介者。多个部门(Department)需要预定会议室,通过中介者协调避免冲突。请补全(1)~(4)。

java 复制代码
// Mediator
interface MeetingMediator {
    void bookRoom(String roomId, String deptName);
    void registerRoom(MeetingRoom room);
}

// ConcreteMediator
class ConferenceScheduler implements MeetingMediator {
    private List<MeetingRoom> rooms = new ArrayList<>();
    private Map<String, String> bookings = new HashMap<>();

    @Override
    public void registerRoom(MeetingRoom room) {
        rooms.add(room);
        System.out.println("注册会议室: " + room.getRoomId());
    }

    @Override
    public void bookRoom(String roomId, String deptName) {
        if (bookings.containsKey(roomId)) {
            System.out.println("会议室 " + roomId + " 已被 " + bookings.get(roomId) + " 预定");
            return;
        }
        bookings.put(roomId, deptName);
        System.out.println("【中介者】批准 " + deptName + " 预定 " + roomId);
        
        // 通知所有会议室更新状态
        for (MeetingRoom room : rooms) {
            if (room.getRoomId().equals(roomId)) {
                (1)______;
            }
        }
    }

    public void cancelBooking(String roomId) {
        bookings.remove(roomId);
        System.out.println("【中介者】取消 " + roomId + " 的预定");
    }
}

// Colleague
abstract class MeetingRoom {
    protected String roomId;
    protected (2)______ mediator;

    public MeetingRoom(String roomId, MeetingMediator mediator) {
        this.roomId = roomId;
        this.mediator = mediator;
    }

    public String getRoomId() { return roomId; }

    // 申请预定:委托给中介者
    public void requestBooking(String deptName) {
        (3)______;
    }

    // 更新状态(由中介者调用)
    public abstract void updateStatus(String status);
}

// ConcreteColleague
class StandardRoom extends MeetingRoom {
    public StandardRoom(String roomId, MeetingMediator mediator) {
        super(roomId, mediator);
    }

    @Override
    public void updateStatus(String status) {
        System.out.println("【标准会议室】" + roomId + " 状态更新为: " + status);
    }
}

public class Client {
    public static void main(String[] args) {
        MeetingMediator scheduler = new ConferenceScheduler();
        MeetingRoom room1 = new StandardRoom("R101", scheduler);
        MeetingRoom room2 = new StandardRoom("R102", scheduler);

        scheduler.registerRoom(room1);
        scheduler.registerRoom(room2);

        // 部门A申请预定R101
        (4)______;
    }
}

答案

  • (1) room.updateStatus("已预定")

  • (2) MeetingMediator

  • (3) mediator.bookRoom(roomId, deptName)

  • (4) room1.requestBooking("部门A")

阅卷要点

  • (1) 必须是 room.updateStatus("已预定")(或类似状态更新调用)。中介者在协调交互时,需要通知相关的 Colleague 更新状态。如果写 room.bookRoom()room.notify()零分(除非题目明确指定方法名)。

  • (2) 必须是 MeetingMediator。Colleague 必须持有 Mediator 的引用,以便将交互委托给中介者。如果写 ConferenceSchedulerList<MeetingRoom>零分

  • (3) 必须是 mediator.bookRoom(roomId, deptName)。Colleague 发送请求时必须委托给 Mediator,不能自己处理或直接与其他 Colleague 通信。如果写 this.bookRoom()room2.bookRoom()零分

  • (4) 必须是 room1.requestBooking("部门A")。客户端通过 Colleague 发起请求,Colleague 再委托给 Mediator。如果写 scheduler.bookRoom("R101", "部门A") 虽然在逻辑上也能跑,但违背了中介者模式的标准交互流程(客户端应与 Colleague 交互,而非直接调用 Mediator)。不过软考中如果直接调用 Mediator 的方法,通常也会给分,但最佳答案是 room1.requestBooking("部门A")


模拟题 3(下午代码填空 --- 补全 UI 组件协调)

某系统使用中介者模式实现表单组件协调。 FormMediator 是中介者, TextBoxButton 是同事。当文本框内容变化时,中介者检查内容是否合法,决定是否启用按钮。请补全(1)~(2)。

java 复制代码
interface FormMediator {
    void textChanged();
    void submit();
}

class LoginFormMediator implements FormMediator {
    private TextBox username;
    private Button submitBtn;

    public void setUsername(TextBox username) { this.username = username; }
    public void setSubmitBtn(Button submitBtn) { this.submitBtn = submitBtn; }

    @Override
    public void textChanged() {
        boolean valid = username.getText().length() >= 6;
        (1)______;  // 根据校验结果设置按钮状态
    }

    @Override
    public void submit() {
        System.out.println("提交: " + username.getText());
    }
}

class TextBox {
    private FormMediator mediator;
    private String text = "";

    public void setMediator(FormMediator m) { this.mediator = m; }

    public void setText(String text) {
        this.text = text;
        (2)______;  // 内容变化时通知中介者
    }

    public String getText() { return text; }
}

class Button {
    private boolean enabled = false;
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        System.out.println("按钮" + (enabled ? "启用" : "禁用"));
    }
    public boolean isEnabled() { return enabled; }
}

答案

  • (1) submitBtn.setEnabled(valid)

  • (2) mediator.textChanged()

阅卷要点

  • (1) 必须是 submitBtn.setEnabled(valid)(或 submitBtn.setEnabled(true/false) 的具体逻辑)。中介者的核心职责就是协调 Colleague 之间的状态联动。如果写 return validSystem.out.println()零分

  • (2) 必须是 mediator.textChanged()。Colleague 状态变化时必须通知 Mediator,由 Mediator 统一处理后续逻辑。如果写 this.textChanged()submitBtn.setEnabled(...)零分(后者直接把交互逻辑写进 Colleague,违背了中介者模式的设计意图)。


十、常见陷阱与注意事项
陷阱 1:误认为中介者是结构型模式

中介者模式属于 行为型 模式,因为它关注的是 对象之间的行为交互 (Mediator 协调 Colleague 之间的通信),而不是类与类之间的结构关系。软考上午题如果问"以下属于结构型模式的是",选项里出现"中介者" 不能选

陷阱 2:Colleague 之间直接引用

这是软考 最高频的失分点 。中介者模式的核心就是 消除 Colleague 之间的直接引用

  • ❌ 错误:
java 复制代码
class User {
    private List<User> friends;  // 直接持有其他 Colleague 的引用
    public void sendTo(User target, String msg) {
        target.receive(msg);  // 直接通信,绕过中介者
    }
}
  • ✅ 正确:
java 复制代码
class User {
    private ChatRoom chatRoom;  // 只持有 Mediator 的引用
    public void send(String msg) {
        chatRoom.sendMessage(msg, this);  // 通过中介者通信
    }
}

如果 Colleague 之间直接引用,那就退化成了普通的网状关系,中介者模式失去了意义。软考代码填空里,Colleague 类中 不能出现其他 Colleague 类型的字段

陷阱 3:与观察者模式混淆

这是软考 最难的辨析题 之一。两者的核心区别:

维度 中介者模式 观察者模式
交互模式 多对多(多个 Colleague 通过 Mediator 互相通信) 一对多(一个 Subject 通知多个 Observer)
通信方向 双向(Colleague ↔ Mediator ↔ Colleague) 单向(Subject → Observer)
触发方式 任意 Colleague 可主动发起交互 只有 Subject 状态变化时触发通知
对象关系 Colleague 之间无直接引用 Observer 之间无直接引用
核心方法 sendMessage()notify()(协调转发) attach()detach()notify()(订阅管理)

终极区分方法:看交互的复杂度和方向。

  • 如果是 一个对象状态变化,自动通知多个依赖对象 → 观察者。

  • 如果是 多个对象之间需要互相通信、协调、转发 → 中介者。

陷阱 4:与外观模式混淆
维度 中介者模式 外观模式
模式分类 行为型 结构型
关注点 行为协调(对象之间怎么交互) 接口封装(怎么简化调用)
客户端角色 客户端与 Colleague 交互,Colleague 委托 Mediator 客户端只与 Facade 交互,不接触子系统
子系统感知 Colleague 知道 Mediator 的存在 Subsystem 不知道 Facade 的存在
双向性 双向(Mediator 调用 Colleague,Colleague 调用 Mediator) 单向(Facade 调用 Subsystem)

如果类图里客户端直接与多个子系统类交互,中间有一个统一入口类 → 外观。如果类图里多个同级对象通过中心类互相通信 → 中介者。

陷阱 5:Mediator 变成 "上帝类"

中介者模式有一个潜在的 缺点:随着系统复杂化,Mediator 可能集中了过多的交互逻辑,变成一个臃肿的 "上帝类"(God Class),难以维护。

  • ❌ 错误:所有业务逻辑都堆在 Mediator 中。
java 复制代码
class ConcreteMediator {
    public void sendMessage(String msg, Colleague sender) {
        // 这里写了 500 行代码,处理各种复杂的交互逻辑
    }
}
  • ✅ 正确:Mediator 只负责 转发和简单协调,复杂逻辑由 Colleague 自己处理。
java 复制代码
class ConcreteMediator {
    public void sendMessage(String msg, Colleague sender) {
        for (Colleague c : colleagues) {
            if (c != sender) c.receive(msg);  // 简单转发
        }
    }
}

软考中如果题目问"中介者模式的缺点",选项中出现 "中介者可能变得过于复杂" → 这是正确答案

陷阱 6:忘记在 Mediator 中注册 Colleague

Colleague 必须注册到 Mediator 中,Mediator 才能对其进行管理和转发:

  • ❌ 错误:
java 复制代码
public static void main(String[] args) {
    ChatRoom chatRoom = new ConcreteChatRoom();
    User user = new NormalUser("张三", chatRoom);
    // 忘记注册!chatRoom.registerUser(user);
    user.send("你好");  // 消息发不出去,因为 Mediator 不知道有这个用户
}
  • ✅ 正确:
java 复制代码
public static void main(String[] args) {
    ChatRoom chatRoom = new ConcreteChatRoom();
    User user = new NormalUser("张三", chatRoom);
    chatRoom.registerUser(user);  // 必须注册
    user.send("你好");
}

软考代码填空时,如果给出了 Client 代码的框架,需要补全的就是 注册 Colleague 到 Mediator 的代码。

陷阱 7:Mediator 接口设计过于具体

Mediator 接口应该定义通用的协调方法,而不是为每个 Colleague 都定义专门的方法:

  • ❌ 错误:
java 复制代码
interface Mediator {
    void userASend(String msg);  // 太具体,只能处理 UserA
    void userBSend(String msg);  // 太具体,只能处理 UserB
}
  • ✅ 正确:
java 复制代码
interface Mediator {
    void sendMessage(String msg, Colleague sender);  // 通用,sender 参数标识发送者
}

软考中,Mediator 接口的方法通常带有 sender 参数(标识哪个 Colleague 发起的请求),以便中介者根据发送者做差异化处理。


十一、总结
要点 内容
定义 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
分类 行为型模式(GoF 23 正式成员)
核心四角色 Mediator(抽象中介者)、ConcreteMediator(具体中介者)、Colleague(抽象同事)、ConcreteColleague(具体同事)
核心思想 将多对多的网状交互解耦为一对多的星型交互,交互逻辑集中在中介者中
解耦效果 N 个对象的网状关系(N×(N-1)/2 条关联)解耦为星型关系(N 条关联)
双向关联 ConcreteMediator 持有所有 Colleague;ConcreteColleague 持有 Mediator
消息转发 ConcreteMediator 接收消息并转发/过滤给相关 Colleague
与观察者模式区别 中介者:多对多交互协调,双向;观察者:一对多通知,单向
与外观模式区别 中介者:行为型,关注交互解耦;外观:结构型,关注接口简化
适用场景 ① 多个对象互相引用形成复杂网状关系 ② 交互逻辑复杂且经常变化 ③ 需要集中控制交互
缺点 中介者可能变成 "上帝类",过于臃肿
软考重点 代码填空(补全 Mediator 转发逻辑 + Colleague 委托调用 + 注册逻辑);与观察者、外观模式的辨析
答题技巧 看到 "多个对象互相引用"、"网状关系解耦"、"集中调度"、"星型结构" → 中介者;看到 Colleague 只持有 Mediator 引用 → 确认中介者

系列预告 :下一篇将讲 备忘录模式 ------ 当需要捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复时,如何在不破坏封装性的前提下实现状态的保存与恢复。备忘录模式与原型模式的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。

相关推荐
饼干哥哥1 天前
一夜之间,全世界的 Agent 能力提高了一个档次
设计模式·agent·代码规范
Kel1 天前
管道、IPC、Socket:你以为的三种技术,其实是同一件事
javascript·设计模式·node.js
我登哥MVP1 天前
进 Gang of Four 设计模式:建造者模式
java·设计模式·建造者模式
进击的前栈2 天前
鸿蒙实战:19 页面全景汇总与 7 大共享设计模式回顾
华为·设计模式·harmonyos
刀法如飞2 天前
Spring Boot 4.1 + JDK 25 DDD开源脚手架,面向 AI 编程
设计模式·架构·ai编程
折哥的程序人生 · 物流技术专研2 天前
第8篇:模板方法模式的优缺点与面试高频考点
java·设计模式·面试·模板方法模式·行为型模式·优缺点·扩充系列
workflower2 天前
人形机器人全身运动控制能力大幅提升
人工智能·深度学习·机器学习·设计模式·机器人·自动化
折哥的程序人生 · 物流技术专研2 天前
Java 23 种设计模式:从踩坑到精通 | 备忘录模式 —— 快照与撤销,给对象装一个“后悔药”
java·设计模式·备忘录模式·行为型模式·java设计模式·从踩坑到精通·撤销重做
Kel2 天前
万物皆 Runnable:LangChain 如何用一个接口统一模型、链与图
人工智能·设计模式·架构