系列定位:软考软件设计师 / 系统架构设计师 — 行为型模式专题第 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(抽象中介者)定义与同事对象通信的接口,声明用于协调同事对象交互的方法(如 sendMessagenotifyinterface 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 代码实现
// 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 代码实现
// 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 代码实现
// 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 组件协调
MediatorChatRoom(聊天室接口)ControlTower(管制塔接口)FormMediator(表单中介者接口)
ConcreteMediatorConcreteChatRoom(转发消息)AirportControlTower(调度跑道)LoginFormMediator(协调组件状态)
ColleagueUser(用户抽象类)Aircraft(飞机抽象类)Component(组件抽象类)
ConcreteColleagueNormalUserVipUserPassengerPlaneCargoPlaneTextBoxPasswordBoxButtonLabel
交互方式群发消息(一对多广播)资源调度(请求-审批-通知)状态联动(输入变化→按钮状态更新)
中介者持有List<User>List<Aircraft> + runwayAvailable各组件引用(直接注入)
同事持有ChatRoom(构造器注入)ControlTower(构造器注入)FormMediator(setter 注入)
发送委托chatRoom.sendMessage(msg, this)controlTower.requestLanding(this)mediator.componentChanged(this)
新增同事创建 + 注册到中介者创建 + 注册到中介者创建 + 设置中介者 + 注入中介者
软考考法下午大题(补全转发逻辑)下午大题(补全调度逻辑)上午选择题(识别组件解耦)

七、中介者模式 vs 观察者模式 vs 外观模式:核心对比
对比项中介者模式观察者模式外观模式
模式分类行为型行为型结构型
核心思想封装多对象交互,将网状关系解耦为星型关系定义对象间一对多依赖,状态变化时自动通知为子系统提供统一接口,简化客户端使用
交互方向多对多 通过中介者转接(双向)一对多 广播(Subject → Observer,单向)客户端 → 外观 → 子系统(单向封装)
对象关系Colleague 之间 不直接引用,只引用 MediatorObserver 之间 无直接引用,只引用 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 持有多个 ColleagueSubject → Observer 单向关联,Subject 持有多个 ObserverFacade → 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)。

// 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)。

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 之间的直接引用

  • ❌ 错误:

class User {
    private List<User> friends;  // 直接持有其他 Colleague 的引用
    public void sendTo(User target, String msg) {
        target.receive(msg);  // 直接通信,绕过中介者
    }
}
  • ✅ 正确:

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 中。

class ConcreteMediator {
    public void sendMessage(String msg, Colleague sender) {
        // 这里写了 500 行代码,处理各种复杂的交互逻辑
    }
}
  • ✅ 正确:Mediator 只负责 转发和简单协调,复杂逻辑由 Colleague 自己处理。

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 才能对其进行管理和转发:

  • ❌ 错误:

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

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 都定义专门的方法:

  • ❌ 错误:

interface Mediator {
    void userASend(String msg);  // 太具体,只能处理 UserA
    void userBSend(String msg);  // 太具体,只能处理 UserB
}
  • ✅ 正确:

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 引用 → 确认中介者

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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐