系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 5 讲
考察分值:上午题 1-2 分,下午题常作为类图识别或代码填空出现
难度等级:⭐⭐⭐☆☆(结构清晰,Mediator 与 Colleague 的关联关系是填空重点)
一、考纲定位与模式定义
1.1 考纲要求
中介者模式在软考中属于 行为型模式 的基础内容。考察形式包括:
-
上午选择题:判断场景描述所属模式;识别中介者模式与观察者模式、外观模式的区别;判断 Mediator、ConcreteMediator、Colleague、ConcreteColleague 四个角色;识别 "多对多交互解耦为一对多" 的设计意图
-
下午设计题 :补全
Colleague中持有Mediator引用的代码;补全ConcreteMediator中协调多个Colleague交互的逻辑;补全Mediator接口中定义通信方法(如sendMessage、notify)的代码;识别类图中 Mediator 与 Colleague 的关联关系
1.2 模式定义
中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
核心意图 :当一个系统中 多个对象之间互相引用 ,形成复杂的 网状关系(N 个对象两两交互,最多需要 N×(N-1)/2 条关联线)时,如果每个对象都直接持有其他对象的引用,会导致:
-
系统耦合度极高,一个对象改动会影响所有相关对象
-
新增对象时需要修改大量现有类
-
对象之间的交互逻辑散落在各个类中,难以维护
中介者模式引入一个 "中介者" 对象,所有对象不再直接互相引用,而是 统一通过中介者通信 。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(抽象中介者) | 定义与同事对象通信的接口,声明用于协调同事对象交互的方法(如 sendMessage、notify) |
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 | NormalUser、VipUser |
PassengerPlane、CargoPlane |
TextBox、PasswordBox、Button、Label |
| 交互方式 | 群发消息(一对多广播) | 资源调度(请求-审批-通知) | 状态联动(输入变化→按钮状态更新) |
| 中介者持有 | 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 的引用,以便将交互委托给中介者。如果写ConferenceScheduler或List<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是中介者,TextBox和Button是同事。当文本框内容变化时,中介者检查内容是否合法,决定是否启用按钮。请补全(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 valid或System.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 引用 → 确认中介者 |
系列预告 :下一篇将讲 备忘录模式 ------ 当需要捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后恢复时,如何在不破坏封装性的前提下实现状态的保存与恢复。备忘录模式与原型模式的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。