用一个"协调中枢"封装对象间的交互,让同事对象之间不再互相认识——网状依赖变星形依赖。
中介者模式(Mediator Pattern)定义:用一个中介对象来封装一组对象之间的交互方式,使各对象之间不再显式地互相引用,从而降低它们之间的耦合度,并且可以独立地改变它们之间的交互规则。
一句话:对象之间不直接对话,所有沟通都经过一个"协调中枢"。它把"多对多"的复杂交互网络,改造成"一对多"的星形结构——每个参与者只认识中介者,不认识其他参与者。这是行为型模式家族中专门解决"对象间通信失控"问题的模式。
本课是行为型模式的第 4 站。我们已经学过责任链(Day 13,把请求沿着链传递)、命令(Day 14,把请求封装成对象)、迭代器(Day 15,统一遍历方式)。今天的中介者与它们不同:它不改变请求的形态,而是改变对象之间通信的组织方式。学完今天的内容,你会理解为什么 Qt 项目里大量 UI 联动代码都适合收敛到一个"连接集中地"。
先看一个真实的软件场景:开发一个多人聊天室。最直觉的写法是——每个用户对象都持有其他所有用户的指针,直接给对方发消息:
// 坏味道版本:没有中介者,用户对象之间直接互相引用
class User {
public:
User(std::string name) : name_(std::move(name)) {}
// 想给谁发消息,就要拿到谁的指针
void sendTo(User* other, const std::string& msg) {
std::cout << name_ << " -> " << other->name() << ": " << msg << "\n";
other->receive(this, msg);
}
void receive(User* from, const std::string& msg) {
std::cout << name_ << " 收到: " << msg << "\n";
}
const std::string& name() const { return name_; }
private:
std::string name_;
};
这段代码有四个致命问题:
再看一个 Qt 场景:一个空调控制面板,滑块、数字输入框、模式下拉框、状态栏四者需要联动——滑块拖动要同步数字框,模式切换要改变温度范围。如果让控件之间直接 connect 成网状,连接线会随着控件数量平方级增长,且牵一发动全身。
两个场景的共性:对象之间的交互规则正在变得复杂,而交互规则和对象本身的逻辑纠缠在一起。这时候需要一个"协调中枢"把通信收拢起来——这就是中介者模式出现的时机。
💡 判断信号:当你的代码里出现大量"对象 A 调用对象 B 的方法,B 又回调 A",或"新增一个对象就要改动 N 个旧对象"时,就是在提示你:通信需要集中管理了。
中介者模式的核心思想就一句话:引入一个独立的"协调中枢",让所有参与者只跟它说话,由它来决定消息怎么流转。
用生活类比理解:
回到软件工程,中介者模式的核心分工是:
为什么能降低耦合?因为耦合的本质是"知道的太多"。原本每个对象要知道其他所有对象的存在和接口,现在只需要知道一个中介者。对象之间的"多对多"关系被压缩成"每个对象对中介者"的"一对多"关系。通信的复杂度没有消失,但它被集中到了唯一一个地方,其他地方都是干净的。
⚠️ 需要注意:中介者模式是把"对象间的耦合"换成了"对象与中介者之间的耦合"。如果交互规则太复杂,中介者会膨胀成"上帝对象"——这是它的固有代价,后面第 ⑩ 节会专门讨论如何避免。
中介者模式的角色结构(用文字版类图表示,箭头表示"依赖/调用"方向):
┌────────────────┐ ┌─────────────────────┐
│ Mediator │ │ Colleague(抽象) │
│ (抽象中介者) │ │ + setMediator() │
│ + notify() │◄──────│ + send() │
└────────────────┘ │ + receive() │
▲ └─────────▲───────────┘
│ │ 继承
┌───────┴────────┐ ┌────────┴────────────┐
│ ConcreteMediator│──────▶│ ConcreteColleague A │
│ (具体中介者) │ 转发 │ ConcreteColleague B │
│ - colleagues[] │ │ ... │
└────────────────┘ └─────────────────────┘
四个角色的职责:
notify(sender, event)),具体交互规则不在接口里,只在实现里。它让"中介者"可以被替换(比如可插拔的调度策略)。send() / receive()),并持有一个中介者的引用。同事只知道中介者,不知道任何其他同事。依赖关系要点:
join() / setControls()),它拥有同事的注册表。用 C++17 实现第 ② 节里的聊天室,用 ChatRoom 作中介者。这个示例完整可编译:
// mediator_chat.cpp —— 编译: g++ -std=c++17 mediator_chat.cpp -o mediator_chat
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class ChatRoom; // 前向声明:User 只需要"知道有这么一个中介者"
// ---------- 同事基类:所有参与聊天的对象 ----------
class User {
public:
User(std::string name, ChatRoom* room)
: name_(std::move(name)), room_(room) {}
virtual ~User() = default;
// 发消息:自己不找任何人,只把消息交给中介者
void send(const std::string& message) {
std::cout << "[" << name_ << "] 发送: " << message << "\n";
room_->broadcast(this, message); // 唯一对外出口
}
// 收消息:由中介者回调
virtual void receive(const User* from, const std::string& message) {
std::cout << "[" << name_ << "] 收到 [" << from->name()
<< "] 的消息: " << message << "\n";
}
const std::string& name() const { return name_; }
protected:
std::string name_;
ChatRoom* room_; // 只依赖中介者,不依赖任何其他 User
};
// ---------- 具体同事:管理员(对收到的消息有额外处理) ----------
class AdminUser : public User {
public:
AdminUser(std::string name, ChatRoom* room)
: User(std::move(name), room) {}
void receive(const User* from, const std::string& message) override {
std::cout << "[管理员 " << name_ << "] 审阅 [" << from->name()
<< "] 的消息: " << message << "\n";
}
};
// ---------- 具体中介者:持有全部同事,集中实现通信规则 ----------
class ChatRoom {
public:
void join(User* user) { users_.push_back(user); }
// 通信规则集中在这里:广播给除发送者以外的所有人
void broadcast(User* sender, const std::string& message) {
for (User* u : users_) {
if (u != sender) {
u->receive(sender, message);
}
}
}
private:
// 只登记不拥有:User 的生命周期由外部(main 的栈)管理,
// 中介者用裸指针避免循环引用和所有权混乱
std::vector<User*> users_;
};
int main() {
ChatRoom room; // 1. 先有中介者
User alice("Alice", &room); // 2. 创建同事,只认识中介者
AdminUser bob("Bob", &room);
room.join(&alice); // 3. 向中介者注册
room.join(&bob);
alice.send("大家好,我是 Alice"); // 4. 同事间通信全部走中介者
bob.send("欢迎 Alice!");
return 0;
}
逐段解释:
User 构造函数只接收自己的名字和 ChatRoom*——它根本不知道其他 User 存在。发送消息时调用 room_->broadcast(this, ...),把转发责任完全交给中介者。AdminUser 只需覆写 receive() 改变"收到消息后的行为",不需要碰任何通信逻辑——扩展点清晰。ChatRoom::broadcast() 是全部交互规则的所在:遍历注册表、跳过发送者、逐个回调。以后要加"禁言""关键词过滤",都只改这里。std::vector<User*> 且不拥有用户——用户活在 main 的栈上,生命周期先于 room 结束(后构造先析构),不存在悬垂指针;若用户需要动态创建,应使用 std::shared_ptr 并让中介者持有 std::weak_ptr,避免循环引用。程序输出:
[Alice] 发送: 大家好,我是 Alice
[管理员 Bob] 审阅 [Alice] 的消息: 大家好,我是 Alice
[Bob] 发送: 欢迎 Alice!
[Alice] 收到 [Bob] 的消息: 欢迎 Alice!
对比第 ② 节的坏代码:新增一个用户,现在只需 join() 一次,所有旧代码零改动——OCP 得到满足;通信规则收进 ChatRoom 一个类,SRP 得到满足。
Qt 的信号槽天然支持对象间通信,但如果控件之间直接互相 connect,连接线仍会形成网状,且联动规则散落各处。本示例用 ControlMediator(继承 QObject)把所有控件信号收拢到一个类里:滑块、数字框、模式下拉框、状态栏彼此不认识,全部由中介者协调。
controlmediator.h
#ifndef CONTROLMEDIATOR_H
#define CONTROLMEDIATOR_H
#include <QObject>
class QSlider;
class QSpinBox;
class QComboBox;
class QLabel;
// 中介者:集中管理所有控件之间的联动,控件之间零耦合
class ControlMediator : public QObject {
Q_OBJECT
public:
explicit ControlMediator(QObject* parent = nullptr);
// 注册控件:中介者只负责协调,不负责创建控件
void setControls(QSlider* slider, QSpinBox* spinBox,
QComboBox* modeBox, QLabel* statusLabel);
private slots:
void onSliderChanged(int value); // 滑块变化 → 同步数字框与状态栏
void onSpinChanged(int value); // 数字框变化 → 同步滑块
void onModeChanged(int index); // 模式变化 → 调整温度范围
private:
QSlider* slider_ = nullptr;
QSpinBox* spinBox_ = nullptr;
QComboBox* modeBox_ = nullptr;
QLabel* statusLabel_ = nullptr;
bool syncing_ = false; // 防回环标志:避免 滑块→数字框→滑块 死循环
};
#endif // CONTROLMEDIATOR_H
controlmediator.cpp
#include "controlmediator.h"
#include <QComboBox>
#include <QLabel>
#include <QSlider>
#include <QSpinBox>
ControlMediator::ControlMediator(QObject* parent) : QObject(parent) {}
void ControlMediator::setControls(QSlider* slider, QSpinBox* spinBox,
QComboBox* modeBox, QLabel* statusLabel) {
slider_ = slider;
spinBox_ = spinBox;
modeBox_ = modeBox;
statusLabel_ = statusLabel;
// ★ 全部信号连接集中在这一个函数里,而不是散落在各控件类中
connect(slider_, &QSlider::valueChanged, this, &ControlMediator::onSliderChanged);
connect(spinBox_, QOverload<int>::of(&QSpinBox::valueChanged),
this, &ControlMediator::onSpinChanged);
connect(modeBox_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ControlMediator::onModeChanged);
onModeChanged(modeBox_->currentIndex()); // 按初始模式初始化界面
}
void ControlMediator::onSliderChanged(int value) {
if (syncing_) return; // 回环保护
syncing_ = true;
spinBox_->setValue(value); // 滑块 → 数字框
statusLabel_->setText(QStringLiteral("当前温度:%1 ℃").arg(value));
syncing_ = false;
}
void ControlMediator::onSpinChanged(int value) {
if (syncing_) return; // 回环保护
syncing_ = true;
slider_->setValue(value); // 数字框 → 滑块
statusLabel_->setText(QStringLiteral("当前温度:%1 ℃").arg(value));
syncing_ = false;
}
void ControlMediator::onModeChanged(int index) {
// 通信规则集中地:不同模式对应不同温度范围
// 制冷 16~30,制热 18~32,通风 0~40
const int minTemp = (index == 0) ? 16 : (index == 1) ? 18 : 0;
const int maxTemp = (index == 0) ? 30 : (index == 1) ? 32 : 40;
slider_->setRange(minTemp, maxTemp);
spinBox_->setRange(minTemp, maxTemp);
spinBox_->setValue(minTemp); // 切换模式时重置温度
statusLabel_->setText(QStringLiteral("模式已切换,温度范围 %1~%2 ℃")
.arg(minTemp).arg(maxTemp));
}
main.cpp
#include "controlmediator.h"
#include <QApplication>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QSlider>
#include <QSpinBox>
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle(QStringLiteral("空调控制面板(中介者模式)"));
// 第一行:模式选择
auto* modeLabel = new QLabel(QStringLiteral("模式:"));
auto* modeBox = new QComboBox;
modeBox->addItems({QStringLiteral("制冷"), QStringLiteral("制热"),
QStringLiteral("通风")});
auto* modeRow = new QHBoxLayout;
modeRow->addWidget(modeLabel);
modeRow->addWidget(modeBox);
modeRow->addStretch();
// 第二行:温度滑块 + 数字输入框
auto* slider = new QSlider(Qt::Horizontal);
slider->setRange(16, 30);
auto* spinBox = new QSpinBox;
spinBox->setRange(16, 30);
spinBox->setSuffix(QStringLiteral(" ℃"));
auto* tempRow = new QHBoxLayout;
tempRow->addWidget(slider, 1);
tempRow->addWidget(spinBox);
// 第三行:状态栏
auto* statusLabel = new QLabel(QStringLiteral("就绪"));
statusLabel->setAlignment(Qt::AlignCenter);
auto* layout = new QVBoxLayout(&window);
layout->addLayout(modeRow);
layout->addLayout(tempRow);
layout->addWidget(statusLabel);
// ★ 关键:四个控件彼此不认识,所有联动逻辑交给中介者
ControlMediator mediator;
mediator.setControls(slider, spinBox, modeBox, statusLabel);
window.show();
return app.exec();
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(TemperaturePanel VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON) # 自动处理 Q_OBJECT 元对象编译
find_package(Qt6 REQUIRED COMPONENTS Widgets) # Qt5: find_package(Qt5 ...)
add_executable(temperature_panel
main.cpp
controlmediator.h
controlmediator.cpp
)
target_link_libraries(temperature_panel PRIVATE Qt6::Widgets) # Qt5: Qt5::Widgets
Qt 5 / Qt 6 说明:本示例代码在 Qt 5 与 Qt 6 下完全一致(QOverload<int>::of(...) 自 Qt 5.7 起可用,用于消除 valueChanged 这类重载信号的歧义);区别仅在 CMake 的 find_package 与 target 名称。注意 ControlMediator mediator; 是栈对象,其槽函数依赖的所有控件都归 window 父子管理,且窗口关闭后 app.exec() 返回、栈对象随后析构,生命周期安全。
以 Qt 示例为主,梳理运行时的完整协作流程:
main() 创建四个控件并放入布局 → 创建 ControlMediator → 调用 setControls()。此时中介者把三条 connect 全部建立,并调用一次 onModeChanged(0),将界面初始化为"制冷模式 16~30℃",状态栏显示"模式已切换"。QSlider 数值变化 → 发射 valueChanged(int) → 信号被中介者的 onSliderChanged() 槽接收 → 槽内 spinBox_->setValue(value) 同步数字框,statusLabel_->setText(...) 更新状态栏。数字框因为被 setValue 赋值也会发射自己的 valueChanged,但 syncing_ 标志让 onSpinChanged() 直接返回,切断回环。QComboBox 发射 currentIndexChanged(int) → 中介者 onModeChanged() 按新模式重新 setRange 滑块和数字框、重置温度、更新状态栏。控件自身完全不知道"模式"这个概念。QSpinBox 发射 valueChanged → onSpinChanged() 反向同步滑块与状态栏(同样受 syncing_ 保护)。至此,任意一个控件的任何变化,都由中介者统一分发到其余控件。纯 C++ 聊天室示例的流程同理:alice.send() → ChatRoom::broadcast() 遍历注册表 → 逐个回调 receive()。消息的"路由"完全由中介者决定,同事对象只是被动接收。
从架构角度剖析中介者模式的四个关键设计决策:
User 的成员只有一个 ChatRoom*——类型系统中根本不存在"另一个用户"。在 Qt 示例里,四个控件类的头文件互相零引用,QSlider 甚至不知道 QSpinBox 存在。对象间原本的 N×(N-1) 条依赖被压缩成 N 条"指向中介者"的依赖。ChatRoom::broadcast() / ControlMediator::onModeChanged() 一个方法。类对修改关闭,系统对扩展开放。User 依赖抽象中介者(其实例是 ChatRoom),中介者依赖抽象同事(User::receive() 虚函数)。两边的依赖都指向"抽象",而不是"具体实现"。后续把中介者换成带日志/加密的包装版,同事代码一行不改。FilteredUser : User——功能每多一种,继承链就多一层,最终类爆炸。中介者方案是组合:过滤规则作为组件放进 ChatRoom(组合),User 根本不需要变化。控制面板里 syncing_ 回环保护、范围规则也都是"组合"进中介者的行为。send() 是否正确转发);中介者可以被单独测试(构造假同事,验证交互规则)。在 Qt 示例中,ControlMediator 不依赖真实窗口也能测试——直接 new 出控件再 setControls(),无需 show()。这正是"交互逻辑与界面细节分离"带来的可测性。回到第 ② 节的坏代码,看需求增长后的演进路径:
User::receive() 里加判断"发消息者是否被禁言"——10 个类各改一遍,任何一处漏改就是 bug。这就是典型的散弹式修改(Shotgun Surgery),是违反 SRP 的经典坏味道。connect(a, ..., b, ...) 的网状连接。改一个联动规则要找到所有相关 connect 逐个修改,漏改一个就出现"改了滑块数字框不跟着动"的诡异现象。共同规律:对象间的交互需求越复杂、参与者越多,无中介者的代码维护成本就越接近平方级增长。而中介者把成本从"平方级分散"变为"线性级集中"——代价只是中介者本身会变复杂,但它是唯一需要复杂的地方。
✅ 适合使用(3~5 个场景):
❌ 不适合使用(2~4 个场景):
⚠️ 过度设计提醒:中介者模式最大的坑是"中介者上帝化"——交互规则过多导致 ConcreteMediator 膨胀成几千行的万能类。缓解手段:把中介者的职责按子系统拆分(多个小中介者)、把规则本身再委托给策略对象、或改用事件总线(Event Bus)让中介者退化为"纯转发"。
选三个最容易混淆的模式对比:
| 对比维度 | 中介者 Mediator | 观察者 Observer | 外观 Facade |
|---|---|---|---|
| 目的 | 集中管理对象之间的交互规则(双向、多对多) | 建立一对多通知关系(单向:主题 → 订阅者) | 为复杂子系统提供简化入口(单向:客户端 → 子系统) |
| 消息流向 | 同事 → 中介者 → 同事(可双向、可路由) | 主题 → 订阅者(单向广播) | 客户端 → 外观 → 子系统(单向委派) |
| 参与者关系 | 同事之间互相不认识,只认中介者 | 订阅者可以互相不认识,只认主题 | 客户端只认外观,子系统内部随便互相引用 |
| Qt 中的对应 | 把多个控件的 connect 集中到一个协调类 | QObject 信号槽、QAbstractItemModel 的 dataChanged 通知 | QNetworkAccessManager(封装 HTTP 细节)、QFileDialog::getOpenFileName 静态函数 |
| 一句话区分 | "所有人找同一个中间人办事" | "一件事通知所有关心它的人" | "给我一个简单的门面,别让我碰复杂的内部" |
Mediator vs Observer:观察者解决"单向通知",中介者解决"双向/多向交互"。Qt 信号槽是观察者思想的实现——但如果两个控件要互相 connect 成环,就会形成网状;中介者则是把这些连接集中到一个地方管理。现实中两者常结合使用:中介者内部用信号槽与同事通信(本课 Qt 示例正是如此)。
Mediator vs Facade:外观是"简化入口",对象之间的通信在子系统内部照旧;中介者是"通信枢纽",参与者之间的通信必须经过它。外观对子系统内部不做任何约束,中介者则从根本上切断了同事间的直接依赖。另外它与 Day 10 学过的外观模式还有一个差异:外观是结构型模式(组织静态结构),中介者是行为型模式(组织运行时行为)。
Mediator vs Command(Day 14):命令把"请求"封装成对象以便撤销/排队;中介者封装的是"对象间的通信关系"。两者可以组合:命令对象由中介者转发执行(比如塔台向飞机下达"等待"指令)。
如实说明:Qt 中没有直接命名为 Mediator 的类,但中介者思想深度渗透在 Qt 的架构实践中:
QDialogButtonBox:它收集各按钮的点击信号,统一发射 accepted() / rejected(),对话框只需连接这两个信号——按钮们互相不认识,按钮与对话框的交互规则被 ButtonBox 这个"小中介者"收拢。Qt Designer 生成的代码里,所有 connect 集中在主窗口构造函数,也是同一思想。QEvent 由 QApplication 统一接收并分发给目标对象(notify() 是核心入口)。各窗口部件不直接互相"投递事件",而是经由 QApplication 这个全局协调中枢——尽管它更多是事件系统架构使然,但"参与者不直接通信、由中枢统一调度"的形态与中介者一致。QAbstractItemModel 的接口操作;Model 变化通过 dataChanged 信号通知 View。二者只依赖抽象接口和信号,不互相持有——Qt 用"模型 + 信号"实现了类似中介者的解耦效果(后序课程会专门讲 Model/View)。给读者的启示:在 Qt 项目里,"主窗口类"或专门的 XxxCoordinator / XxxController 类经常天然承担中介者职责。当你发现主窗口的构造函数里 connect 越来越多、越来越难维护时,正是考虑"把连接和联动规则抽到一个独立中介者类"的信号。
Q1:中介者模式和观察者模式有什么区别?在 Qt 中如何选择?
A:观察者模式是单向的"一对多"通知(主题 → 订阅者),订阅者不回应主题;中介者模式是"多对多"的双向交互管理,参与者通过中介者互相通信,通信规则集中在中介者中。Qt 中,如果只是"某个状态变了通知一批控件刷新",用信号槽(观察者)足够;如果多个控件之间存在复杂的双向联动(A 变影响 B,B 变又影响 A,且规则随模式切换),就应该把连接收拢到一个中介者类中管理,避免网状 connect。
Q2:中介者模式的主要缺点是什么?如何避免"上帝中介者"?
A:缺点是中介者可能承担过多交互逻辑而膨胀,成为难以维护的上帝对象;同时引入了一层间接调用,牺牲少量性能。避免手段:①按子系统拆分成多个小中介者,每个只负责一组相关交互;②把复杂规则委托给独立的策略/规则对象,中介者只做编排;③若交互主要是"广播通知"而非"定向路由",改用事件总线,让中介者退化为纯转发器。
Q3:中介者模式如何保证对象生命周期安全?C++ 中同事与中介者谁拥有谁?
A:中介者通常不拥有同事对象(只登记引用),同事的生命周期由外部管理,这样避免循环引用和所有权混乱。若同事动态创建:方案一,同事持有 std::shared_ptr<Mediator>,中介者持有 std::vector<std::weak_ptr<Colleague>>,同事销毁后中介者自动跳过失效项;方案二,明确约定销毁顺序(先销毁所有同事再销毁中介者)。在 Qt 中,控件归 parent 父子管理,中介者若也挂在同一父对象下,则利用 Qt 的对象树统一析构,天然安全。
Q4:中介者模式和外观模式都"包装"了一堆对象,它们本质区别是什么?
A:方向与职责不同。外观(Facade)是给客户端一个简化入口,客户端 → 外观 → 子系统,子系统内部照常互相通信;中介者(Mediator)是切断参与者之间的直接通信,所有消息必须经中介者转发。外观侧重"简化使用",不改变对象间关系;中介者侧重"集中交互规则",从根本上重构了对象间的关系。实践中可以同时使用:外观对外,中介者对内。
Q5:在 Qt 中实现中介者时,如何处理信号回环(A 更新 B,B 又触发 A)?
A:常用三种手段:①syncing_ 布尔标志(本课示例采用),在同步期间让反向槽直接返回;②使用 QSignalBlocker 临时屏蔽某个控件的信号(QSignalBlocker blocker(spinBox););③blockSignals(true) / blockSignals(false) 手动控制。推荐优先用 QSignalBlocker(RAII 自动恢复),比手写标志更不易出错。注意:回环保护是中介者模式在信号槽环境下的必修课,面试中常作为考察细节的点。
练习:登录表单联动(Qt,建议 20~30 分钟)
用中介者模式实现一个登录对话框:
QLineEdit、密码 QLineEdit(EchoMode 为 Password)、"登录"按钮、状态 QLabel;QTimer::singleShot)恢复可编辑并清空密码。设计要求:LoginMediator : QObject 持有全部控件指针,所有 connect 与规则都在它里面;对话框只负责创建控件和布局。
💡 提示 1:先想清楚哪些信号需要收集——QLineEdit::textChanged 与 QPushButton::clicked;提示 2:规则③的"只读状态"可以用 setReadOnly(),注意按钮在只读期间也应禁用。
(想挑战纯 C++ 版?实现一个"拍卖行":拍卖师作中介者,多个买家通过拍卖师出价,拍卖师决定当前最高价并广播。主要锻炼所有权设计——思考中介者如何安全持有动态创建的买家。)