[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-22":26,"\u002Farticles\u002Fdesign-pattern-22\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-22\u002Fadjacent":72,"\u002Fcategories":83,"render-design-pattern-22":109},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113934247046680578","design-pattern-22","设计模式修炼之路 ㉒ 中介者模式——网状依赖变星型","网状依赖变星型、N×(N-1) 收成 N 条、聊天室聚餐事故、中介者 vs 观察者","2113897628365230081",1,1550,6591,17,"2024-11-01T18:45:07","> 行为型第九站。\n\n上一篇存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。\n\n把网状依赖收拢成星型——**所有人只认识群主一个**——这就是中介者模式。\n\n## 一、中介者模式是什么\n\nGoF 的原始定义：\n\n> Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.\n> （用一个中介对象来封装一系列对象的交互。中介者使各对象不需要显式地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。）\n\n关键词是\"封装交互\"。各对象之间的交互规则——谁的消息发给谁、私聊怎么路由、新人进群要不要广播——全部从成员身上抽走，装进一个专职的中介对象。\n\n现实中最贴切的类比是**机场塔台**：飞机与飞机之间从不直接通话，所有飞机只跟塔台说话，塔台认识空域里的每一架飞机，负责协调所有起降。你不会允许两架飞机自己商量谁先降落——同样的道理，复杂系统里你也不该允许对象之间私自拉群。\n\n四个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Mediator（抽象中介者）\u003C\u002Ftd>\u003Ctd>定义同事对象与中介者通信的接口\u003C\u002Ftd>\u003Ctd>ChatMediator\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteMediator（具体中介者）\u003C\u002Ftd>\u003Ctd>认识所有同事，负责协调它们之间的交互\u003C\u002Ftd>\u003Ctd>ChatRoom（群主）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Colleague（抽象同事类）\u003C\u002Ftd>\u003Ctd>每个同事只持有中介者的引用，发消息一律委托中介者\u003C\u002Ftd>\u003Ctd>User\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteColleague（具体同事类）\u003C\u002Ftd>\u003Ctd>具体的交互参与者，互相不认识\u003C\u002Ftd>\u003Ctd>CommonUser \u002F VipUser\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n拓扑学一点说：**中介者把 N×(N-1) 条关系线压缩成了 N 条**。三人群从 6 条线变 3 条，百人群从 9900 条变 100 条——依赖从平方级降到线性级，这是这个模式全部价值的数学表达。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **网状变星型**：依赖条数从 N×(N-1) 降为 N，\"来一个新人全员改代码\"的惨剧从机制上根除——新人只注册给群主，老成员零改动。\n2. **交互规则集中**：\"谁该收到什么\"的知识全部住在群主家。改路由规则（比如\"群主消息不发给发送者本人\"）只改一处，而不是改遍所有成员。\n3. **同事类可复用**：CommonUser 对中介者内部一无所知，只认识一个抽象接口——把聊天室换成公会频道、客服系统，同事类一行不改。\n4. **类间解耦**：张三和李四互相不知道对方存在，删掉李四，张三毫无感知。\n\n**缺点：**\n\n1. **上帝对象风险**：所有交互逻辑都往 ConcreteMediator 里堆，群主类从 30 行膨胀到 3000 行，变成无所不知无所不管的上帝对象——GoF 原书都专门警告了这一点，这是该模式最大的坑。\n2. **中介者单点**：所有消息都过它，它挂了全群哑火；高并发场景下，中心节点就是性能瓶颈。\n3. **同事与中介者双向依赖**：User 持有 ChatMediator，ChatRoom 遍历 User 列表——比\"谁也不认识谁\"的单向依赖紧，这是星型的固有代价。\n\n## 三、什么时候用\n\n判断标准：**多个对象之间存在网状交互，改一个牵动全员，且交互规则本身经常变。**\n\n- **聊天室 \u002F IM 服务器**：消息路由、成员管理——本文场景\n- **GUI 事件协调**：对话框里\"按钮禁用依赖文本框内容、下拉框联动刷新列表\"——GoF 原书的经典例子\n- **MVC 中的 Controller**：Model 和 View 互不相识，全靠 Controller 穿针引线（㉖ 会展开细讲）\n- **游戏房间 \u002F 匹配大厅**：玩家状态同步、房间广播\n- **微服务注册中心**：服务间调用不写死地址，先问 Nacos\u002FEureka\n\n反面对照：**两个对象交互**——一对一根本不需要中介，直接调用最清晰；**三个对象、关系稳定**——两两直连只有 3 条线，引中介者反而多一层间接性；**交互规则简单到没有**——中介就退化成传话游戏，除了费劲毫无收益。\n\n## 四、实现方式：聊天室\n\n### 4.1 反面教材：人人互联的网状地狱\n\n先看最直觉的做法——谁要联系谁，就持有谁的引用：\n\n```java\npublic class BadUser {\n\n    private final String name;\n    private final List\u003CBadUser> contacts = new ArrayList\u003C>();\n\n    public void addContact(BadUser user) {\n        contacts.add(user);\n    }\n\n    public void sendGroupMessage(String message) {\n        for (BadUser contact : contacts) {\n            contact.receive(name, message);\n        }\n    }\n\n    \u002F\u002F 私聊？前提是你手里有对方的引用——不认识的人聊不了\n    public void sendPrivateMessage(BadUser target, String message) {\n        target.receive(name, message);\n    }\n    \u002F\u002F ...\n}\n```\n\n三个用户入群，六条关系线全部手工维护。运行一下，见证一场\"聚餐事故\"：\n\n```\n========== 反面教材：人人互联的网状地狱 ==========\n  [张三 → 群发] 大家好，我是张三！\n    [李四 收到来自 张三] 大家好，我是张三！\n    [王五 收到来自 张三] 大家好，我是张三！\n  [李四 → 群发] 欢迎欢迎~\n    [张三 收到来自 李四] 欢迎欢迎~\n    [王五 收到来自 李四] 欢迎欢迎~\n  —— 赵六进群：老成员理论上全体加一遍引用，张三手滑漏了 ——\n  [赵六 → 群发] 我是新人赵六，请多关照\n    [张三 收到来自 赵六] 我是新人赵六，请多关照\n    [李四 收到来自 赵六] 我是新人赵六，请多关照\n    [王五 收到来自 赵六] 我是新人赵六，请多关照\n  [张三 → 群发] 周末聚餐，来的扣 1\n    [李四 收到来自 张三] 周末聚餐，来的扣 1\n    [王五 收到来自 张三] 周末聚餐，来的扣 1\n  —— 赵六：？？？聚餐怎么没人叫我 ——\n  [张三 → 李四] 你看赵六的头像是不是很眼熟（私聊）\n    [李四 收到来自 张三] 你看赵六的头像是不是很眼熟\n```\n\n灵异事件复盘：赵六能收到李四王五的消息、也能把自己的消息发给所有人——唯独张三发的聚餐他收不到。因为**张三忘了 `addContact(zhaoliu)`**。群消息遍历的是发送者自己的通讯录，通讯录不同步，消息就丢。这种 bug 的可怕之处在于：不报错、不抛异常，消息悄无声息地消失，排查全靠人肉对账。\n\n再看结构层面的账：四人群 = 12 条关系线，每条都要手工建立、手工维护。赵六进群那一刻，张三、李四、王五的代码全都得改——开闭原则阵亡。\n\n### 4.2 正解：只认群主的星型结构\n\n四个角色依次登场。抽象中介者：\n\n```java\npublic interface ChatMediator {\n\n    void register(User user);\n\n    void sendMessage(String from, String message);\n\n    void sendPrivateMessage(String from, String to, String message);\n}\n```\n\n抽象同事类——注意它只认识中介者，不认识任何具体同事：\n\n```java\npublic abstract class User {\n\n    protected final ChatMediator mediator;\n    protected final String name;\n\n    protected User(ChatMediator mediator, String name) {\n        this.mediator = mediator;\n        this.name = name;\n    }\n\n    public void send(String message) {\n        System.out.println(\"  [\" + name + \" → 群发] \" + message);\n        mediator.sendMessage(name, message);\n    }\n\n    public void privateSend(String to, String message) {\n        System.out.println(\"  [\" + name + \" → \" + to + \"] \" + message);\n        mediator.sendPrivateMessage(name, to, message);\n    }\n\n    \u002F\u002F 包级私有：只有中介者（同包）能调\n    abstract void receive(String from, String message);\n    \u002F\u002F ...\n}\n```\n\n具体中介者——所有\"谁该收到什么\"的知识，全部收拢到这里：\n\n```java\npublic class ChatRoom implements ChatMediator {\n\n    private final List\u003CUser> users = new ArrayList\u003C>();\n\n    @Override\n    public void register(User user) {\n        for (User member : users) {\n            member.notifyNewMember(user.getName());\n        }\n        users.add(user);\n    }\n\n    @Override\n    public void sendMessage(String from, String message) {\n        for (User user : users) {\n            if (!user.getName().equals(from)) {\n                user.receive(from, message);\n            }\n        }\n    }\n\n    @Override\n    public void sendPrivateMessage(String from, String to, String message) {\n        for (User user : users) {\n            if (user.getName().equals(to)) {\n                user.receive(from, message + \"（私聊）\");\n                return;\n            }\n        }\n        System.out.println(\"  [群主] 查无此人：\" + to + \"，私聊失败\");\n    }\n}\n```\n\n具体同事就是纯纯的参与者，收消息的姿势各不相同：\n\n```java\npublic class CommonUser extends User {\n    @Override\n    void receive(String from, String message) {\n        System.out.println(\"    [普通用户 \" + name + \" 收到来自 \" + from + \"] \" + message);\n    }\n}\n\npublic class VipUser extends User {\n    @Override\n    void receive(String from, String message) {\n        System.out.println(\"    [VIP \" + name + \" 火速收到来自 \" + from + \"] \" + message);\n    }\n}\n```\n\n运行：\n\n```\n========== 正解：只认群主的星型结构 ==========\n  [群主] 欢迎 张三 加入聊天室\n  [群主] 欢迎 李四 加入聊天室\n    [系统] 张三 得知 李四 加入了群聊\n  [群主] 欢迎 王五 加入聊天室\n    [系统] 张三 得知 王五 加入了群聊\n    [系统] 李四 得知 王五 加入了群聊\n  [张三 → 群发] 大家好，我是张三！\n    [VIP 李四 火速收到来自 张三] 大家好，我是张三！\n    [普通用户 王五 收到来自 张三] 大家好，我是张三！\n  [李四 → 张三] 你的头像我好像在哪见过\n    [普通用户 张三 收到来自 李四] 你的头像我好像在哪见过（私聊）\n  [王五 → 群发] 周末有人组队打球吗\n    [普通用户 张三 收到来自 王五] 周末有人组队打球吗\n    [VIP 李四 火速收到来自 王五] 周末有人组队打球吗\n  —— 赵六进群：只跟群主打交道，老成员零改动 ——\n  [群主] 欢迎 赵六 加入聊天室\n    [系统] 张三 得知 赵六 加入了群聊\n    [系统] 李四 得知 赵六 加入了群聊\n    [系统] 王五 得知 赵六 加入了群聊\n  [赵六 → 群发] 我是新人赵六，请多关照\n    [普通用户 张三 收到来自 赵六] 我是新人赵六，请多关照\n    [VIP 李四 火速收到来自 赵六] 我是新人赵六，请多关照\n    [普通用户 王五 收到来自 赵六] 我是新人赵六，请多关照\n  [赵六 → 李四] 老哥带带我\n    [VIP 李四 火速收到来自 赵六] 老哥带带我（私聊）\n  [赵六 → 田七] 在吗？\n  [群主] 查无此人：田七，私聊失败\n```\n\n对照事故现场：反面教材里赵六进群要 6 次 `addContact` 还漏了 1 次；正解里一行 `chatRoom.register(zhaoliu)` 全搞定，全员零改动。查无此人？群主统一兜底，不会再有消息无声消失。\n\n### 4.3 三处细节\n\n**细节一：不对称的认识关系。** 中介者持有所有同事的列表，而每个同事只持有一个中介者引用——并且只认识 `ChatMediator` 抽象接口。星型的中心认识所有分叉，分叉只认识中心。这就是\"换中介者同事类零改动\"的底层原因：对同事来说，中介者就是个黑盒接口，聊天室换成客服系统，只要接口对得上，同事浑然不觉。\n\n**细节二：包级私有的 receive。** `abstract void receive(...)` 故意不加 public——同包的中介者能调，外界想绕过群主直接戳成员 receive？编译器第一个不答应。把\"必须经过中介者\"从团队纪律升级成语法约束，和 ⑳ 迭代器把 next 藏在 Iterator 协议里是同一个思路：**走正门，堵后门。**\n\n**细节三：register 里的顺序。** 先 `notifyNewMember` 通知老成员，再 `users.add`——新人不会收到\"欢迎自己\"的系统消息。这种边角规则也只在中介者一个地方定义——反面教材里它得散落在每个成员的 addContact 里，漏一处就是灵异事件。\n\n## 五、在框架里找它\n\n1. **MVC 里的 Controller**：GoF 原书就把 MVC 列为中介者模式的应用——View 不直接改 Model，Model 数据变化也不直接命令 View 刷新，Controller 居中协调两者。这是日常开发里最常见的\"隐形中介者\"，㉖ 框架模式篇会展开细讲。\n\n2. **Spring 的事件机制 \u002F Guava EventBus**：`ApplicationEventPublisher.publishEvent()` 本质是\"中介者接口泛化成事件流\"——同事连中介者有哪些具体方法都不用知道，只会发事件和听事件。这是中介者与观察者的混血，第六节细说。\n\n3. **MQ 消息中间件（Kafka \u002F RocketMQ）**：生产者和消费者互不认识，都只连 broker——跨进程的中介者。broker 按主题路由消息，和群主按名单转发消息，干的是同一件事。区别只是中介者从进程内的对象变成了独立部署的基础设施。\n\n4. **微服务注册中心（Nacos \u002F Eureka \u002F Zookeeper）**：服务之间调用不写死对方地址，先问注册中心拿实例列表——把\"谁在哪儿\"的知识收拢到中心节点，和群主持有成员名单一模一样。星型拓扑撑起了整个微服务世界。\n\n## 六、中介者 vs 观察者——终于还是走到了这一步\n\n这个系列里两个\"解耦谁通知谁\"的模式终于碰面了。都解决对象间通信问题，基因却完全不同：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>维度\u003C\u002Fth>\u003Cth>中介者模式 ㉒\u003C\u002Fth>\u003Cth>观察者模式 ⑯\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>关系形态\u003C\u002Ftd>\u003Ctd>多对多，双向协商\u003C\u002Ftd>\u003Ctd>一对多，单向广播\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>谁认识谁\u003C\u002Ftd>\u003Ctd>中介者认识所有同事，同事只认识中介者\u003C\u002Ftd>\u003Ctd>主题不认识具体观察者，只持有接口列表\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>解耦的对象\u003C\u002Ftd>\u003Ctd>交互的**双方**（A 和 B 互不认识）\u003C\u002Ftd>\u003Ctd>通知的**发送方与接收方**\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>典型场景\u003C\u002Ftd>\u003Ctd>\"A 私聊 B\"、\"按钮点击后列表联动刷新\"\u003C\u002Ftd>\u003Ctd>\"数据变了，关心的人各自更新\"\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>交互逻辑住在哪\u003C\u002Ftd>\u003Ctd>中介者（显式的路由代码）\u003C\u002Ftd>\u003Ctd>分散在各个观察者（各自决定怎么响应）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>主要风险\u003C\u002Ftd>\u003Ctd>中介者膨胀成上帝对象\u003C\u002Ftd>\u003Ctd>观察者风暴、通知链失控\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n血缘关系其实很近：**把中介者接口从 sendMessage \u002F sendPrivateMessage 泛化成 publish(event)，把同事的 receive 泛化成 onEvent——中介者就退化成了事件总线**。EventBus 和 Spring 事件机制正是这个杂交产物：中介者的星型拓扑 + 观察者的广播语义。\n\n选用口诀一句话：**有来有回的协商用中介者，单向的广而告之用观察者。**\n\n## 写在最后\n\n- 网状依赖的本质：N×(N-1) 条关系线，平方级的维护成本，漏一条就是无声的灵异事件\n- 中介者的手术：所有\"谁该收到什么\"的知识收拢进群主，同事只剩一条到中心的线\n- 认识关系的不对称是灵魂：中心认识所有分叉，分叉只认识抽象中心\n- 最大的坑是上帝对象——群主类膨胀失控，届时要按业务拆中介者，而不是往里硬塞\n- 和观察者的分界：协商用中介者，广播用观察者，事件总线是两者的混血\n\n最后一行输出里藏了个伏笔：`[群主] 查无此人：田七，私聊失败`——群主老实交代了失败。可如果中介方法返回的是 `null`，调用方就得 `if (user != null)` 判一路空，判空代码满天飞，漏判一处就是 `NullPointerException`。**能不能造一个\"什么都不做的对象\"顶替 null，让判空彻底从代码里消失？**——空对象模式，下一篇见。\n","\u003Cblockquote>\n \u003Cp>行为型第九站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。\u003C\u002Fp>\n\u003Cp>把网状依赖收拢成星型——\u003Cstrong>所有人只认识群主一个\u003C\u002Fstrong>——这就是中介者模式。\u003C\u002Fp>\n\u003Ch2>一、中介者模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. （用一个中介对象来封装一系列对象的交互。中介者使各对象不需要显式地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>关键词是\"封装交互\"。各对象之间的交互规则——谁的消息发给谁、私聊怎么路由、新人进群要不要广播——全部从成员身上抽走，装进一个专职的中介对象。\u003C\u002Fp>\n\u003Cp>现实中最贴切的类比是\u003Cstrong>机场塔台\u003C\u002Fstrong>：飞机与飞机之间从不直接通话，所有飞机只跟塔台说话，塔台认识空域里的每一架飞机，负责协调所有起降。你不会允许两架飞机自己商量谁先降落——同样的道理，复杂系统里你也不该允许对象之间私自拉群。\u003C\u002Fp>\n\u003Cp>四个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Mediator（抽象中介者）\u003C\u002Ftd>\n   \u003Ctd>定义同事对象与中介者通信的接口\u003C\u002Ftd>\n   \u003Ctd>ChatMediator\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteMediator（具体中介者）\u003C\u002Ftd>\n   \u003Ctd>认识所有同事，负责协调它们之间的交互\u003C\u002Ftd>\n   \u003Ctd>ChatRoom（群主）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Colleague（抽象同事类）\u003C\u002Ftd>\n   \u003Ctd>每个同事只持有中介者的引用，发消息一律委托中介者\u003C\u002Ftd>\n   \u003Ctd>User\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteColleague（具体同事类）\u003C\u002Ftd>\n   \u003Ctd>具体的交互参与者，互相不认识\u003C\u002Ftd>\n   \u003Ctd>CommonUser \u002F VipUser\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>拓扑学一点说：\u003Cstrong>中介者把 N×(N-1) 条关系线压缩成了 N 条\u003C\u002Fstrong>。三人群从 6 条线变 3 条，百人群从 9900 条变 100 条——依赖从平方级降到线性级，这是这个模式全部价值的数学表达。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>网状变星型\u003C\u002Fstrong>：依赖条数从 N×(N-1) 降为 N，\"来一个新人全员改代码\"的惨剧从机制上根除——新人只注册给群主，老成员零改动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>交互规则集中\u003C\u002Fstrong>：\"谁该收到什么\"的知识全部住在群主家。改路由规则（比如\"群主消息不发给发送者本人\"）只改一处，而不是改遍所有成员。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同事类可复用\u003C\u002Fstrong>：CommonUser 对中介者内部一无所知，只认识一个抽象接口——把聊天室换成公会频道、客服系统，同事类一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类间解耦\u003C\u002Fstrong>：张三和李四互相不知道对方存在，删掉李四，张三毫无感知。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>上帝对象风险\u003C\u002Fstrong>：所有交互逻辑都往 ConcreteMediator 里堆，群主类从 30 行膨胀到 3000 行，变成无所不知无所不管的上帝对象——GoF 原书都专门警告了这一点，这是该模式最大的坑。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中介者单点\u003C\u002Fstrong>：所有消息都过它，它挂了全群哑火；高并发场景下，中心节点就是性能瓶颈。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同事与中介者双向依赖\u003C\u002Fstrong>：User 持有 ChatMediator，ChatRoom 遍历 User 列表——比\"谁也不认识谁\"的单向依赖紧，这是星型的固有代价。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>多个对象之间存在网状交互，改一个牵动全员，且交互规则本身经常变。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>聊天室 \u002F IM 服务器\u003C\u002Fstrong>：消息路由、成员管理——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>GUI 事件协调\u003C\u002Fstrong>：对话框里\"按钮禁用依赖文本框内容、下拉框联动刷新列表\"——GoF 原书的经典例子\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MVC 中的 Controller\u003C\u002Fstrong>：Model 和 View 互不相识，全靠 Controller 穿针引线（㉖ 会展开细讲）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>游戏房间 \u002F 匹配大厅\u003C\u002Fstrong>：玩家状态同步、房间广播\u003C\u002Fli>\n \u003Cli>\u003Cstrong>微服务注册中心\u003C\u002Fstrong>：服务间调用不写死地址，先问 Nacos\u002FEureka\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：\u003Cstrong>两个对象交互\u003C\u002Fstrong>——一对一根本不需要中介，直接调用最清晰；\u003Cstrong>三个对象、关系稳定\u003C\u002Fstrong>——两两直连只有 3 条线，引中介者反而多一层间接性；\u003Cstrong>交互规则简单到没有\u003C\u002Fstrong>——中介就退化成传话游戏，除了费劲毫无收益。\u003C\u002Fp>\n\u003Ch2>四、实现方式：聊天室\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：人人互联的网状地狱\u003C\u002Fh3>\n\u003Cp>先看最直觉的做法——谁要联系谁，就持有谁的引用：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadUser {\n\n    private final String name;\n    private final List&lt;BadUser&gt; contacts = new ArrayList&lt;&gt;();\n\n    public void addContact(BadUser user) {\n        contacts.add(user);\n    }\n\n    public void sendGroupMessage(String message) {\n        for (BadUser contact : contacts) {\n            contact.receive(name, message);\n        }\n    }\n\n    \u002F\u002F 私聊？前提是你手里有对方的引用——不认识的人聊不了\n    public void sendPrivateMessage(BadUser target, String message) {\n        target.receive(name, message);\n    }\n    \u002F\u002F ...\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>三个用户入群，六条关系线全部手工维护。运行一下，见证一场\"聚餐事故\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>========== 反面教材：人人互联的网状地狱 ==========\n  [张三 → 群发] 大家好，我是张三！\n    [李四 收到来自 张三] 大家好，我是张三！\n    [王五 收到来自 张三] 大家好，我是张三！\n  [李四 → 群发] 欢迎欢迎~\n    [张三 收到来自 李四] 欢迎欢迎~\n    [王五 收到来自 李四] 欢迎欢迎~\n  —— 赵六进群：老成员理论上全体加一遍引用，张三手滑漏了 ——\n  [赵六 → 群发] 我是新人赵六，请多关照\n    [张三 收到来自 赵六] 我是新人赵六，请多关照\n    [李四 收到来自 赵六] 我是新人赵六，请多关照\n    [王五 收到来自 赵六] 我是新人赵六，请多关照\n  [张三 → 群发] 周末聚餐，来的扣 1\n    [李四 收到来自 张三] 周末聚餐，来的扣 1\n    [王五 收到来自 张三] 周末聚餐，来的扣 1\n  —— 赵六：？？？聚餐怎么没人叫我 ——\n  [张三 → 李四] 你看赵六的头像是不是很眼熟（私聊）\n    [李四 收到来自 张三] 你看赵六的头像是不是很眼熟\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>灵异事件复盘：赵六能收到李四王五的消息、也能把自己的消息发给所有人——唯独张三发的聚餐他收不到。因为\u003Cstrong>张三忘了 \u003Ccode>addContact(zhaoliu)\u003C\u002Fcode>\u003C\u002Fstrong>。群消息遍历的是发送者自己的通讯录，通讯录不同步，消息就丢。这种 bug 的可怕之处在于：不报错、不抛异常，消息悄无声息地消失，排查全靠人肉对账。\u003C\u002Fp>\n\u003Cp>再看结构层面的账：四人群 = 12 条关系线，每条都要手工建立、手工维护。赵六进群那一刻，张三、李四、王五的代码全都得改——开闭原则阵亡。\u003C\u002Fp>\n\u003Ch3>4.2 正解：只认群主的星型结构\u003C\u002Fh3>\n\u003Cp>四个角色依次登场。抽象中介者：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public interface ChatMediator {\n\n    void register(User user);\n\n    void sendMessage(String from, String message);\n\n    void sendPrivateMessage(String from, String to, String message);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>抽象同事类——注意它只认识中介者，不认识任何具体同事：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public abstract class User {\n\n    protected final ChatMediator mediator;\n    protected final String name;\n\n    protected User(ChatMediator mediator, String name) {\n        this.mediator = mediator;\n        this.name = name;\n    }\n\n    public void send(String message) {\n        System.out.println(\"  [\" + name + \" → 群发] \" + message);\n        mediator.sendMessage(name, message);\n    }\n\n    public void privateSend(String to, String message) {\n        System.out.println(\"  [\" + name + \" → \" + to + \"] \" + message);\n        mediator.sendPrivateMessage(name, to, message);\n    }\n\n    \u002F\u002F 包级私有：只有中介者（同包）能调\n    abstract void receive(String from, String message);\n    \u002F\u002F ...\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体中介者——所有\"谁该收到什么\"的知识，全部收拢到这里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class ChatRoom implements ChatMediator {\n\n    private final List&lt;User&gt; users = new ArrayList&lt;&gt;();\n\n    @Override\n    public void register(User user) {\n        for (User member : users) {\n            member.notifyNewMember(user.getName());\n        }\n        users.add(user);\n    }\n\n    @Override\n    public void sendMessage(String from, String message) {\n        for (User user : users) {\n            if (!user.getName().equals(from)) {\n                user.receive(from, message);\n            }\n        }\n    }\n\n    @Override\n    public void sendPrivateMessage(String from, String to, String message) {\n        for (User user : users) {\n            if (user.getName().equals(to)) {\n                user.receive(from, message + \"（私聊）\");\n                return;\n            }\n        }\n        System.out.println(\"  [群主] 查无此人：\" + to + \"，私聊失败\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体同事就是纯纯的参与者，收消息的姿势各不相同：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class CommonUser extends User {\n    @Override\n    void receive(String from, String message) {\n        System.out.println(\"    [普通用户 \" + name + \" 收到来自 \" + from + \"] \" + message);\n    }\n}\n\npublic class VipUser extends User {\n    @Override\n    void receive(String from, String message) {\n        System.out.println(\"    [VIP \" + name + \" 火速收到来自 \" + from + \"] \" + message);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>========== 正解：只认群主的星型结构 ==========\n  [群主] 欢迎 张三 加入聊天室\n  [群主] 欢迎 李四 加入聊天室\n    [系统] 张三 得知 李四 加入了群聊\n  [群主] 欢迎 王五 加入聊天室\n    [系统] 张三 得知 王五 加入了群聊\n    [系统] 李四 得知 王五 加入了群聊\n  [张三 → 群发] 大家好，我是张三！\n    [VIP 李四 火速收到来自 张三] 大家好，我是张三！\n    [普通用户 王五 收到来自 张三] 大家好，我是张三！\n  [李四 → 张三] 你的头像我好像在哪见过\n    [普通用户 张三 收到来自 李四] 你的头像我好像在哪见过（私聊）\n  [王五 → 群发] 周末有人组队打球吗\n    [普通用户 张三 收到来自 王五] 周末有人组队打球吗\n    [VIP 李四 火速收到来自 王五] 周末有人组队打球吗\n  —— 赵六进群：只跟群主打交道，老成员零改动 ——\n  [群主] 欢迎 赵六 加入聊天室\n    [系统] 张三 得知 赵六 加入了群聊\n    [系统] 李四 得知 赵六 加入了群聊\n    [系统] 王五 得知 赵六 加入了群聊\n  [赵六 → 群发] 我是新人赵六，请多关照\n    [普通用户 张三 收到来自 赵六] 我是新人赵六，请多关照\n    [VIP 李四 火速收到来自 赵六] 我是新人赵六，请多关照\n    [普通用户 王五 收到来自 赵六] 我是新人赵六，请多关照\n  [赵六 → 李四] 老哥带带我\n    [VIP 李四 火速收到来自 赵六] 老哥带带我（私聊）\n  [赵六 → 田七] 在吗？\n  [群主] 查无此人：田七，私聊失败\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照事故现场：反面教材里赵六进群要 6 次 \u003Ccode>addContact\u003C\u002Fcode> 还漏了 1 次；正解里一行 \u003Ccode>chatRoom.register(zhaoliu)\u003C\u002Fcode> 全搞定，全员零改动。查无此人？群主统一兜底，不会再有消息无声消失。\u003C\u002Fp>\n\u003Ch3>4.3 三处细节\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：不对称的认识关系。\u003C\u002Fstrong> 中介者持有所有同事的列表，而每个同事只持有一个中介者引用——并且只认识 \u003Ccode>ChatMediator\u003C\u002Fcode> 抽象接口。星型的中心认识所有分叉，分叉只认识中心。这就是\"换中介者同事类零改动\"的底层原因：对同事来说，中介者就是个黑盒接口，聊天室换成客服系统，只要接口对得上，同事浑然不觉。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：包级私有的 receive。\u003C\u002Fstrong> \u003Ccode>abstract void receive(...)\u003C\u002Fcode> 故意不加 public——同包的中介者能调，外界想绕过群主直接戳成员 receive？编译器第一个不答应。把\"必须经过中介者\"从团队纪律升级成语法约束，和 ⑳ 迭代器把 next 藏在 Iterator 协议里是同一个思路：\u003Cstrong>走正门，堵后门。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：register 里的顺序。\u003C\u002Fstrong> 先 \u003Ccode>notifyNewMember\u003C\u002Fcode> 通知老成员，再 \u003Ccode>users.add\u003C\u002Fcode>——新人不会收到\"欢迎自己\"的系统消息。这种边角规则也只在中介者一个地方定义——反面教材里它得散落在每个成员的 addContact 里，漏一处就是灵异事件。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>\u003Cstrong>MVC 里的 Controller\u003C\u002Fstrong>：GoF 原书就把 MVC 列为中介者模式的应用——View 不直接改 Model，Model 数据变化也不直接命令 View 刷新，Controller 居中协调两者。这是日常开发里最常见的\"隐形中介者\"，㉖ 框架模式篇会展开细讲。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Spring 的事件机制 \u002F Guava EventBus\u003C\u002Fstrong>：\u003Ccode>ApplicationEventPublisher.publishEvent()\u003C\u002Fcode> 本质是\"中介者接口泛化成事件流\"——同事连中介者有哪些具体方法都不用知道，只会发事件和听事件。这是中介者与观察者的混血，第六节细说。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>MQ 消息中间件（Kafka \u002F RocketMQ）\u003C\u002Fstrong>：生产者和消费者互不认识，都只连 broker——跨进程的中介者。broker 按主题路由消息，和群主按名单转发消息，干的是同一件事。区别只是中介者从进程内的对象变成了独立部署的基础设施。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>微服务注册中心（Nacos \u002F Eureka \u002F Zookeeper）\u003C\u002Fstrong>：服务之间调用不写死对方地址，先问注册中心拿实例列表——把\"谁在哪儿\"的知识收拢到中心节点，和群主持有成员名单一模一样。星型拓扑撑起了整个微服务世界。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>六、中介者 vs 观察者——终于还是走到了这一步\u003C\u002Fh2>\n\u003Cp>这个系列里两个\"解耦谁通知谁\"的模式终于碰面了。都解决对象间通信问题，基因却完全不同：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>维度\u003C\u002Fth>\n   \u003Cth>中介者模式 ㉒\u003C\u002Fth>\n   \u003Cth>观察者模式 ⑯\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>关系形态\u003C\u002Ftd>\n   \u003Ctd>多对多，双向协商\u003C\u002Ftd>\n   \u003Ctd>一对多，单向广播\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>谁认识谁\u003C\u002Ftd>\n   \u003Ctd>中介者认识所有同事，同事只认识中介者\u003C\u002Ftd>\n   \u003Ctd>主题不认识具体观察者，只持有接口列表\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>解耦的对象\u003C\u002Ftd>\n   \u003Ctd>交互的**双方**（A 和 B 互不认识）\u003C\u002Ftd>\n   \u003Ctd>通知的**发送方与接收方**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型场景\u003C\u002Ftd>\n   \u003Ctd>\"A 私聊 B\"、\"按钮点击后列表联动刷新\"\u003C\u002Ftd>\n   \u003Ctd>\"数据变了，关心的人各自更新\"\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>交互逻辑住在哪\u003C\u002Ftd>\n   \u003Ctd>中介者（显式的路由代码）\u003C\u002Ftd>\n   \u003Ctd>分散在各个观察者（各自决定怎么响应）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>主要风险\u003C\u002Ftd>\n   \u003Ctd>中介者膨胀成上帝对象\u003C\u002Ftd>\n   \u003Ctd>观察者风暴、通知链失控\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>血缘关系其实很近：\u003Cstrong>把中介者接口从 sendMessage \u002F sendPrivateMessage 泛化成 publish(event)，把同事的 receive 泛化成 onEvent——中介者就退化成了事件总线\u003C\u002Fstrong>。EventBus 和 Spring 事件机制正是这个杂交产物：中介者的星型拓扑 + 观察者的广播语义。\u003C\u002Fp>\n\u003Cp>选用口诀一句话：\u003Cstrong>有来有回的协商用中介者，单向的广而告之用观察者。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>网状依赖的本质：N×(N-1) 条关系线，平方级的维护成本，漏一条就是无声的灵异事件\u003C\u002Fli>\n \u003Cli>中介者的手术：所有\"谁该收到什么\"的知识收拢进群主，同事只剩一条到中心的线\u003C\u002Fli>\n \u003Cli>认识关系的不对称是灵魂：中心认识所有分叉，分叉只认识抽象中心\u003C\u002Fli>\n \u003Cli>最大的坑是上帝对象——群主类膨胀失控，届时要按业务拆中介者，而不是往里硬塞\u003C\u002Fli>\n \u003Cli>和观察者的分界：协商用中介者，广播用观察者，事件总线是两者的混血\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>最后一行输出里藏了个伏笔：\u003Ccode>[群主] 查无此人：田七，私聊失败\u003C\u002Fcode>——群主老实交代了失败。可如果中介方法返回的是 \u003Ccode>null\u003C\u002Fcode>，调用方就得 \u003Ccode>if (user != null)\u003C\u002Fcode> 判一路空，判空代码满天飞，漏判一处就是 \u003Ccode>NullPointerException\u003C\u002Fcode>。\u003Cstrong>能不能造一个\"什么都不做的对象\"顶替 null，让判空彻底从代码里消失？\u003C\u002Fstrong>——空对象模式，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113932151245246465","design-pattern-16","设计模式修炼之路 ⑯ 观察者模式——事件订阅与广播","一对多依赖、订阅退订自由、生产环境三块拼图、Spring 事件机制、观察者 vs 发布订阅",3150,6331,16,"2024-07-07T14:57:29",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113934307482406914","design-pattern-23","设计模式修炼之路 ㉓ 空对象模式——让判空从代码里消失","非 GoF、用空对象消灭 null 判断、十亿美元错误、NOPLogger 与 emptyList、空对象 vs Optional",1620,4606,12,"2024-11-20T12:21:47",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113932265082851329","design-pattern-18","设计模式修炼之路 ⑱ 命令模式——把请求封装成对象","把请求封装成对象、增量式撤销、白送的排队与重放、Executor 的铁证",2089,7080,18,"2024-08-09T12:36:13",{"code":4,"message":5,"data":73},{"prev":74,"next":82},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":36,"publishedAt":81},"2113934123146940417","design-pattern-21","设计模式修炼之路 ㉑ 备忘录模式——不破坏封装的存档术","不破坏封装的存档术、宽窄接口、Git 与 Savepoint、命令+备忘录合订",3711,6474,"2024-10-08T16:37:17",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},{"code":4,"message":5,"data":84},[85,91,96,102],{"id":86,"name":87,"slug":88,"description":89,"sortOrder":4,"articleCount":90},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":92,"slug":93,"description":94,"sortOrder":4,"articleCount":95},"设计模式","design-pattern","设计模式系列","34",{"id":97,"name":98,"slug":99,"description":100,"sortOrder":33,"articleCount":101},"1946000000000000002","随笔","essay","生活与思考","0",{"id":103,"name":104,"slug":105,"description":106,"sortOrder":107,"articleCount":108},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":110,"toc":111},"\u003Cblockquote>\n \u003Cp>行为型第九站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇存完档，去聊天室放松一下。聊天室里有个老问题：用户 A 想发消息给 B 和 C，B 想回 A，C 想私聊 A……每个用户都得直接持有其他所有用户的引用，N 个人就是 N×(N-1) 条关系线，来一个新人，全员改代码。\u003C\u002Fp>\n\u003Cp>把网状依赖收拢成星型——\u003Cstrong>所有人只认识群主一个\u003C\u002Fstrong>——这就是中介者模式。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、中介者模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. （用一个中介对象来封装一系列对象的交互。中介者使各对象不需要显式地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>关键词是\"封装交互\"。各对象之间的交互规则——谁的消息发给谁、私聊怎么路由、新人进群要不要广播——全部从成员身上抽走，装进一个专职的中介对象。\u003C\u002Fp>\n\u003Cp>现实中最贴切的类比是\u003Cstrong>机场塔台\u003C\u002Fstrong>：飞机与飞机之间从不直接通话，所有飞机只跟塔台说话，塔台认识空域里的每一架飞机，负责协调所有起降。你不会允许两架飞机自己商量谁先降落——同样的道理，复杂系统里你也不该允许对象之间私自拉群。\u003C\u002Fp>\n\u003Cp>四个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Mediator（抽象中介者）\u003C\u002Ftd>\n   \u003Ctd>定义同事对象与中介者通信的接口\u003C\u002Ftd>\n   \u003Ctd>ChatMediator\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteMediator（具体中介者）\u003C\u002Ftd>\n   \u003Ctd>认识所有同事，负责协调它们之间的交互\u003C\u002Ftd>\n   \u003Ctd>ChatRoom（群主）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Colleague（抽象同事类）\u003C\u002Ftd>\n   \u003Ctd>每个同事只持有中介者的引用，发消息一律委托中介者\u003C\u002Ftd>\n   \u003Ctd>User\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteColleague（具体同事类）\u003C\u002Ftd>\n   \u003Ctd>具体的交互参与者，互相不认识\u003C\u002Ftd>\n   \u003Ctd>CommonUser \u002F VipUser\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>拓扑学一点说：\u003Cstrong>中介者把 N×(N-1) 条关系线压缩成了 N 条\u003C\u002Fstrong>。三人群从 6 条线变 3 条，百人群从 9900 条变 100 条——依赖从平方级降到线性级，这是这个模式全部价值的数学表达。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>网状变星型\u003C\u002Fstrong>：依赖条数从 N×(N-1) 降为 N，\"来一个新人全员改代码\"的惨剧从机制上根除——新人只注册给群主，老成员零改动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>交互规则集中\u003C\u002Fstrong>：\"谁该收到什么\"的知识全部住在群主家。改路由规则（比如\"群主消息不发给发送者本人\"）只改一处，而不是改遍所有成员。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同事类可复用\u003C\u002Fstrong>：CommonUser 对中介者内部一无所知，只认识一个抽象接口——把聊天室换成公会频道、客服系统，同事类一行不改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>类间解耦\u003C\u002Fstrong>：张三和李四互相不知道对方存在，删掉李四，张三毫无感知。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>上帝对象风险\u003C\u002Fstrong>：所有交互逻辑都往 ConcreteMediator 里堆，群主类从 30 行膨胀到 3000 行，变成无所不知无所不管的上帝对象——GoF 原书都专门警告了这一点，这是该模式最大的坑。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中介者单点\u003C\u002Fstrong>：所有消息都过它，它挂了全群哑火；高并发场景下，中心节点就是性能瓶颈。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>同事与中介者双向依赖\u003C\u002Fstrong>：User 持有 ChatMediator，ChatRoom 遍历 User 列表——比\"谁也不认识谁\"的单向依赖紧，这是星型的固有代价。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>多个对象之间存在网状交互，改一个牵动全员，且交互规则本身经常变。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>聊天室 \u002F IM 服务器\u003C\u002Fstrong>：消息路由、成员管理——本文场景\u003C\u002Fli>\n \u003Cli>\u003Cstrong>GUI 事件协调\u003C\u002Fstrong>：对话框里\"按钮禁用依赖文本框内容、下拉框联动刷新列表\"——GoF 原书的经典例子\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MVC 中的 Controller\u003C\u002Fstrong>：Model 和 View 互不相识，全靠 Controller 穿针引线（㉖ 会展开细讲）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>游戏房间 \u002F 匹配大厅\u003C\u002Fstrong>：玩家状态同步、房间广播\u003C\u002Fli>\n \u003Cli>\u003Cstrong>微服务注册中心\u003C\u002Fstrong>：服务间调用不写死地址，先问 Nacos\u002FEureka\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：\u003Cstrong>两个对象交互\u003C\u002Fstrong>——一对一根本不需要中介，直接调用最清晰；\u003Cstrong>三个对象、关系稳定\u003C\u002Fstrong>——两两直连只有 3 条线，引中介者反而多一层间接性；\u003Cstrong>交互规则简单到没有\u003C\u002Fstrong>——中介就退化成传话游戏，除了费劲毫无收益。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：聊天室\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：人人互联的网状地狱\u003C\u002Fh3>\n\u003Cp>先看最直觉的做法——谁要联系谁，就持有谁的引用：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadUser\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String name;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">BadUser\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> contacts \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> addContact\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(BadUser \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        contacts.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendGroupMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (BadUser contact \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> contacts) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            contact.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 私聊？前提是你手里有对方的引用——不认识的人聊不了\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendPrivateMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(BadUser \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">target\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        target.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>三个用户入群，六条关系线全部手工维护。运行一下，见证一场\"聚餐事故\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：人人互联的网状地狱 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [张三 → 群发] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [李四 收到来自 张三] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [王五 收到来自 张三] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [李四 → 群发] 欢迎欢迎~\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [张三 收到来自 李四] 欢迎欢迎~\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [王五 收到来自 李四] 欢迎欢迎~\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  —— 赵六进群：老成员理论上全体加一遍引用，张三手滑漏了 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [赵六 → 群发] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [张三 收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [李四 收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [王五 收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [张三 → 群发] 周末聚餐，来的扣 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [李四 收到来自 张三] 周末聚餐，来的扣 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [王五 收到来自 张三] 周末聚餐，来的扣 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  —— 赵六：？？？聚餐怎么没人叫我 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [张三 → 李四] 你看赵六的头像是不是很眼熟（私聊）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [李四 收到来自 张三] 你看赵六的头像是不是很眼熟\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>灵异事件复盘：赵六能收到李四王五的消息、也能把自己的消息发给所有人——唯独张三发的聚餐他收不到。因为\u003Cstrong>张三忘了 \u003Ccode>addContact(zhaoliu)\u003C\u002Fcode>\u003C\u002Fstrong>。群消息遍历的是发送者自己的通讯录，通讯录不同步，消息就丢。这种 bug 的可怕之处在于：不报错、不抛异常，消息悄无声息地消失，排查全靠人肉对账。\u003C\u002Fp>\n\u003Cp>再看结构层面的账：四人群 = 12 条关系线，每条都要手工建立、手工维护。赵六进群那一刻，张三、李四、王五的代码全都得改——开闭原则阵亡。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：只认群主的星型结构\u003C\u002Fh3>\n\u003Cp>四个角色依次登场。抽象中介者：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ChatMediator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> register\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(User \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendPrivateMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">to\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>抽象同事类——注意它只认识中介者，不认识任何具体同事：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> User\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ChatMediator mediator;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String name;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> User\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(ChatMediator \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">mediator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mediator \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mediator;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> send\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" → 群发] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mediator.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sendMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> privateSend\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">to\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" → \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> to \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mediator.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sendPrivateMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(name, to, message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 包级私有：只有中介者（同包）能调\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F ...\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体中介者——所有\"谁该收到什么\"的知识，全部收拢到这里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ChatRoom\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ChatMediator\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">User\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> users \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> register\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(User \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (User member \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> users) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            member.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">notifyNewMember\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        users.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (User user \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> users) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">user.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(from)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                user.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(from, message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendPrivateMessage\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">to\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (User user \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> users) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (user.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(to)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                user.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(from, message \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"（私聊）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [群主] 查无此人：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> to \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"，私聊失败\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体同事就是纯纯的参与者，收消息的姿势各不相同：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> CommonUser\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> User\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"    [普通用户 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 收到来自 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> from \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> VipUser\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> User\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> receive\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">from\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">message\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"    [VIP \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 火速收到来自 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> from \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> message);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 正解：只认群主的星型结构 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [群主] 欢迎 张三 加入聊天室\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [群主] 欢迎 李四 加入聊天室\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 张三 得知 李四 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [群主] 欢迎 王五 加入聊天室\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 张三 得知 王五 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 李四 得知 王五 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [张三 → 群发] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [VIP 李四 火速收到来自 张三] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [普通用户 王五 收到来自 张三] 大家好，我是张三！\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [李四 → 张三] 你的头像我好像在哪见过\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [普通用户 张三 收到来自 李四] 你的头像我好像在哪见过（私聊）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [王五 → 群发] 周末有人组队打球吗\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [普通用户 张三 收到来自 王五] 周末有人组队打球吗\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [VIP 李四 火速收到来自 王五] 周末有人组队打球吗\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  —— 赵六进群：只跟群主打交道，老成员零改动 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [群主] 欢迎 赵六 加入聊天室\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 张三 得知 赵六 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 李四 得知 赵六 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [系统] 王五 得知 赵六 加入了群聊\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [赵六 → 群发] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [普通用户 张三 收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [VIP 李四 火速收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [普通用户 王五 收到来自 赵六] 我是新人赵六，请多关照\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [赵六 → 李四] 老哥带带我\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    [VIP 李四 火速收到来自 赵六] 老哥带带我（私聊）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [赵六 → 田七] 在吗？\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [群主] 查无此人：田七，私聊失败\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照事故现场：反面教材里赵六进群要 6 次 \u003Ccode>addContact\u003C\u002Fcode> 还漏了 1 次；正解里一行 \u003Ccode>chatRoom.register(zhaoliu)\u003C\u002Fcode> 全搞定，全员零改动。查无此人？群主统一兜底，不会再有消息无声消失。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">4.3 三处细节\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>细节一：不对称的认识关系。\u003C\u002Fstrong> 中介者持有所有同事的列表，而每个同事只持有一个中介者引用——并且只认识 \u003Ccode>ChatMediator\u003C\u002Fcode> 抽象接口。星型的中心认识所有分叉，分叉只认识中心。这就是\"换中介者同事类零改动\"的底层原因：对同事来说，中介者就是个黑盒接口，聊天室换成客服系统，只要接口对得上，同事浑然不觉。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节二：包级私有的 receive。\u003C\u002Fstrong> \u003Ccode>abstract void receive(...)\u003C\u002Fcode> 故意不加 public——同包的中介者能调，外界想绕过群主直接戳成员 receive？编译器第一个不答应。把\"必须经过中介者\"从团队纪律升级成语法约束，和 ⑳ 迭代器把 next 藏在 Iterator 协议里是同一个思路：\u003Cstrong>走正门，堵后门。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>\u003Cstrong>细节三：register 里的顺序。\u003C\u002Fstrong> 先 \u003Ccode>notifyNewMember\u003C\u002Fcode> 通知老成员，再 \u003Ccode>users.add\u003C\u002Fcode>——新人不会收到\"欢迎自己\"的系统消息。这种边角规则也只在中介者一个地方定义——反面教材里它得散落在每个成员的 addContact 里，漏一处就是灵异事件。\u003C\u002Fp>\n\u003Ch2 id=\"toc-8\">五、在框架里找它\u003C\u002Fh2>\n\u003Col>\n \u003Cli>\n  \u003Cp>\u003Cstrong>MVC 里的 Controller\u003C\u002Fstrong>：GoF 原书就把 MVC 列为中介者模式的应用——View 不直接改 Model，Model 数据变化也不直接命令 View 刷新，Controller 居中协调两者。这是日常开发里最常见的\"隐形中介者\"，㉖ 框架模式篇会展开细讲。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>Spring 的事件机制 \u002F Guava EventBus\u003C\u002Fstrong>：\u003Ccode>ApplicationEventPublisher.publishEvent()\u003C\u002Fcode> 本质是\"中介者接口泛化成事件流\"——同事连中介者有哪些具体方法都不用知道，只会发事件和听事件。这是中介者与观察者的混血，第六节细说。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>MQ 消息中间件（Kafka \u002F RocketMQ）\u003C\u002Fstrong>：生产者和消费者互不认识，都只连 broker——跨进程的中介者。broker 按主题路由消息，和群主按名单转发消息，干的是同一件事。区别只是中介者从进程内的对象变成了独立部署的基础设施。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\n  \u003Cp>\u003Cstrong>微服务注册中心（Nacos \u002F Eureka \u002F Zookeeper）\u003C\u002Fstrong>：服务之间调用不写死对方地址，先问注册中心拿实例列表——把\"谁在哪儿\"的知识收拢到中心节点，和群主持有成员名单一模一样。星型拓扑撑起了整个微服务世界。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-9\">六、中介者 vs 观察者——终于还是走到了这一步\u003C\u002Fh2>\n\u003Cp>这个系列里两个\"解耦谁通知谁\"的模式终于碰面了。都解决对象间通信问题，基因却完全不同：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>维度\u003C\u002Fth>\n   \u003Cth>中介者模式 ㉒\u003C\u002Fth>\n   \u003Cth>观察者模式 ⑯\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>关系形态\u003C\u002Ftd>\n   \u003Ctd>多对多，双向协商\u003C\u002Ftd>\n   \u003Ctd>一对多，单向广播\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>谁认识谁\u003C\u002Ftd>\n   \u003Ctd>中介者认识所有同事，同事只认识中介者\u003C\u002Ftd>\n   \u003Ctd>主题不认识具体观察者，只持有接口列表\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>解耦的对象\u003C\u002Ftd>\n   \u003Ctd>交互的**双方**（A 和 B 互不认识）\u003C\u002Ftd>\n   \u003Ctd>通知的**发送方与接收方**\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型场景\u003C\u002Ftd>\n   \u003Ctd>\"A 私聊 B\"、\"按钮点击后列表联动刷新\"\u003C\u002Ftd>\n   \u003Ctd>\"数据变了，关心的人各自更新\"\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>交互逻辑住在哪\u003C\u002Ftd>\n   \u003Ctd>中介者（显式的路由代码）\u003C\u002Ftd>\n   \u003Ctd>分散在各个观察者（各自决定怎么响应）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>主要风险\u003C\u002Ftd>\n   \u003Ctd>中介者膨胀成上帝对象\u003C\u002Ftd>\n   \u003Ctd>观察者风暴、通知链失控\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>血缘关系其实很近：\u003Cstrong>把中介者接口从 sendMessage \u002F sendPrivateMessage 泛化成 publish(event)，把同事的 receive 泛化成 onEvent——中介者就退化成了事件总线\u003C\u002Fstrong>。EventBus 和 Spring 事件机制正是这个杂交产物：中介者的星型拓扑 + 观察者的广播语义。\u003C\u002Fp>\n\u003Cp>选用口诀一句话：\u003Cstrong>有来有回的协商用中介者，单向的广而告之用观察者。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>网状依赖的本质：N×(N-1) 条关系线，平方级的维护成本，漏一条就是无声的灵异事件\u003C\u002Fli>\n \u003Cli>中介者的手术：所有\"谁该收到什么\"的知识收拢进群主，同事只剩一条到中心的线\u003C\u002Fli>\n \u003Cli>认识关系的不对称是灵魂：中心认识所有分叉，分叉只认识抽象中心\u003C\u002Fli>\n \u003Cli>最大的坑是上帝对象——群主类膨胀失控，届时要按业务拆中介者，而不是往里硬塞\u003C\u002Fli>\n \u003Cli>和观察者的分界：协商用中介者，广播用观察者，事件总线是两者的混血\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>最后一行输出里藏了个伏笔：\u003Ccode>[群主] 查无此人：田七，私聊失败\u003C\u002Fcode>——群主老实交代了失败。可如果中介方法返回的是 \u003Ccode>null\u003C\u002Fcode>，调用方就得 \u003Ccode>if (user != null)\u003C\u002Fcode> 判一路空，判空代码满天飞，漏判一处就是 \u003Ccode>NullPointerException\u003C\u002Fcode>。\u003Cstrong>能不能造一个\"什么都不做的对象\"顶替 null，让判空彻底从代码里消失？\u003C\u002Fstrong>——空对象模式，下一篇见。\u003C\u002Fp>",[112,115,118,121,124,128,131,134,137,140],{"id":113,"text":114,"level":107},"toc-1","一、中介者模式是什么",{"id":116,"text":117,"level":107},"toc-2","二、优点与缺点",{"id":119,"text":120,"level":107},"toc-3","三、什么时候用",{"id":122,"text":123,"level":107},"toc-4","四、实现方式：聊天室",{"id":125,"text":126,"level":127},"toc-5","4.1 反面教材：人人互联的网状地狱",3,{"id":129,"text":130,"level":127},"toc-6","4.2 正解：只认群主的星型结构",{"id":132,"text":133,"level":127},"toc-7","4.3 三处细节",{"id":135,"text":136,"level":107},"toc-8","五、在框架里找它",{"id":138,"text":139,"level":107},"toc-9","六、中介者 vs 观察者——终于还是走到了这一步",{"id":141,"text":142,"level":107},"toc-10","写在最后"]