设计模式与生活⑳:楼里的事都找前台,你就再没见过师傅

周四,到工位第一件事,先打开 AI 的对话框。

需求评审昨天就过了,把描述贴进去,让它先出一版。它回得快,边界情况顺手列了一串。我改了两处,跑通,交给测试。

下午三点多,线上告警群弹出消息。日志拉下来几百行,整段贴进去。它标了三处可疑的地方,问题就在第二处。顺着它标的往下查,半个多小时,修好了,告警也消了。

六点半,需求上线,待办列表一条条划掉,空了。

关电脑那一下,心里没什么动静。

刚工作那年不是这个感觉------为一个列表加载慢,我能调一晚上,第二天在工位上跟人讲三遍,连"怎么发现是缓存里一个名字拼错了"这一步,都不肯略过。现在一天干完两件事,回头一件都记不住。

老家有句话,雨过地皮干。说的是雨落下来,地皮一干,跟没下过一样。这一天,就有点这个意思。

对话框开了又关,一天下来,活都干完了

事办完了,心里却没留下什么。这种空落落,前几天也来过一次。

那天晚上回家,楼道里黑着。声控灯拍了两下手,没亮。

给物业打电话,前台说师傅一会儿上去看看。二十来分钟后,门外有脚步声,灯亮了。我从猫眼里看了一眼,人已经下楼了。

还有一回,门禁卡刷不开,也是打那个电话。前台说卡失效了,让管门禁的保安在系统里重新登记了一下。我再刷,就好了。前阵子快递丢件,也是找前台调的监控。

楼里的事,一件件都办成了。修灯的师傅、管门禁的保安、调监控的人,我一个也没见过。

今天聊的中介者模式(Mediator),管的就是这回事:楼里的每户、每个师傅,都不直接找对方,所有事都先经过前台,由它转达、协调。系列第二十篇。

01 谁都认识谁

最早没有前台的时候,谁有事谁自己找人。业主认识电工,电工认识保安,保安认识水工------每个人都得把楼里其他人装进自己兜里。

java 复制代码
class Owner {
    private final Electrician electrician;
    private final Guard guard;

    Owner(Electrician electrician, Guard guard) {
        this.electrician = electrician;
        this.guard = guard;
    }

    void lightBroken() {
        electrician.fixLight(guard);  // 电工要贴通知,得把保安一起带进去
    }
}

一个人的事,牵出一串人

这个写法能跑通。灯修好了,通知也贴了。

只是那个 guard 参数,是从业主手里递过去的。电工本来只管修灯,现在得知道保安在哪、怎么喊。再往下,电工要让保安贴通知,保安贴完还得跟电工回一句,电工再转告业主------这条链就越拉越长。

四个角色,两两之间都可能直接找对方,六条线;每条线两头各存一份,就是十二份。再加一个人,五个人,十条线、二十份。

现实里,多一个人只是多存一个号码;代码里,多一个人,前面每个人的构造函数、方法参数都得跟着改。

02 设一个前台

前台不修灯,也不刷卡。它只做一件事:谁来说事,它转给该去的人。

先看每个角色的样子:

java 复制代码
interface FrontDesk {
    void report(String from, String type, String detail);
}

abstract class Neighbor {
    protected final FrontDesk desk;
    protected final String name;

    Neighbor(String name, FrontDesk desk) {
        this.name = name;
        this.desk = desk;
    }

    void report(String type, String detail) {
        desk.report(name, type, detail);   // 有事,只跟一个人说
    }

    abstract void receive(String from, String detail);
}

手里只有前台的号码

业主、电工、保安、水工,都照这个模子来。谁也不知道谁的电话,只知道前台的号码。

前台长这样:

java 复制代码
import java.util.LinkedHashMap;
import java.util.Map;

class FrontDeskImpl implements FrontDesk {
    private final Map<String, Neighbor> people = new LinkedHashMap<>();

    void join(Neighbor n) { people.put(n.name, n); }

    public void report(String from, String type, String detail) {
        System.out.println(from + " → 前台|" + type + ":" + detail);
        String to;
        switch (type) {
            case "电":
                to = "电工";
                break;
            case "水":
                to = "水工";
                break;
            case "门禁":
            case "通知":
                to = "保安";
                break;
            case "回访":
                to = "业主";
                break;
            default:
                to = null;
                break;
        }
        if (to == null) {
            System.out.println("  前台:这类事对不上人,先记在本子上");
            return;
        }
        System.out.println("  前台转给" + to);
        people.get(to).receive(from, detail);
    }
}

一张路由表,谁的事转给谁

电工收到"楼道灯不亮",上楼一看,总闸跳了,推上去。它不知道保安是谁,但它知道"要贴通知"这件事该报给前台:

java 复制代码
class Electrician extends Neighbor {
    Electrician(FrontDesk desk) { super("电工", desk); }

    void receive(String from, String detail) {
        System.out.println("  [电工] 上楼看了一眼:总闸跳了,推上去,灯亮了");
        report("通知", "楼道灯修好了,今晚十点停电检修,单元门贴一张");
    }
}

修完灯,把下一件事交给前台

把前面几段拼起来跑一遍,楼道灯这一段打印出来是这样:

css 复制代码
业主 → 前台|电:楼道灯不亮
  前台转给电工
  [电工] 上楼看了一眼:总闸跳了,推上去,灯亮了
电工 → 前台|通知:楼道灯修好了,今晚十点停电检修,单元门贴一张
  前台转给保安
  [保安] 在单元门贴了通知:楼道灯修好了,今晚十点停电检修,单元门贴一张
保安 → 前台|回访:楼道灯修好了,通知也贴了
  前台转给业主
  [业主] 回访:楼道灯修好了,通知也贴了

运行结果(节选),不是代码

门禁和水管两段跑出来是同一个结构,就不重复贴了。

业主、保安、水工这几个类没单独贴,都是照上面的模子填的:收到消息,处理一下,再把结果报回前台。

全程没有一行"电工找保安"或者"业主找水工"。电工只认识前台,保安也只认识前台。路线绕了一点,线省下来了。

四个角色,四条线。再加一个人,五条线。

线从六条变成四条,事还是那些事

03 前台只转达,不替你做主

先把失真点挑明。

第一处:现实里的前台是人,会忘事、会漏单。代码里的中介者是一张路由表,它不会自己忘;它漏了,是写代码的人漏了。

第二处更要紧:中介者只转达,不替你做主。它只决定这事归谁,不决定这事该怎么办。

师傅说这根线老化了,得整根换,报价三百。前台只能把话带到,修不修还是你的事。换不换、花多少钱,还得你点头。

AI 比前台多走了一步。

它不只是把你的话变成代码,它还替你想:哪行日志可疑、哪个方案能上、这个告警要不要半夜爬起来处理。

所以拿中介者来比 AI,只借了那层结构------所有事都往一个点收,再由那个点发出去。真正让人心里发空的,不是这个结构,是判断也一起放到了那个点上。

以前查一个问题,你会先看监控曲线,再翻最近合并的代码,再问一句运维昨晚动过什么。这条链路是你自己走的,走到哪一步,心里都有数。

现在链路还在,只是被压缩成了一个输入框:贴进去,等结论。

结论是对的,事也办成了。可你没走那条路,脑子里就没留下路。

中介者能替你转达,替不了你判断;能让你少打几个电话,替不了你到场。

中介者还有个副作用,代码里叫上帝类。

前台什么都管:业主找她,师傅找她,连保洁领垃圾袋也找她。她请假那天,整栋楼的事都卡住。

你什么都问 AI,也是一样。它答得快,你就越来越少问别人,越来越少自己查。哪天它给错了,你连该找谁、该翻哪一页,都想不起来。

成就感这个东西,本来就不在"活干完了"那一栏,在"我知道它是怎么好的"那一栏。

至于替代这件事,我也没有答案。能确定的是,中介者模式里,前台没有取代任何一个师傅,它只是让师傅们不必互相认识。AI 现在做的,比转达多,比替代少------它把活干了,判断还得你签字。签字签久了,不自己动脑,那才是真把自己交出去。

顺带把容易混的三个说清。

外观(Day⑨)是用的人只对着一个入口,往里走,单向;里面的零件之间不必认识。中介者是同事之间来回协调,去和回都经过中间那个人。

观察者(Day⑧)是一个变化通知很多人,被通知的人彼此不相干。中介者管的是关系:谁跟谁本来要互相认识,现在都不用了。

代理(Day⑦)是挡在一个对象前面,一对一。

外观收口,观察者广播,代理挡人,中介者把一张网收成一个点。

框架里,很少有哪个现成的类直接叫 Mediator,因为它是一种写法,不是基础设施。

最常见的落地在界面上:一个表单里,"提交"能不能点,取决于三个输入框填没填、两个勾选勾没勾。按钮不去挨个问输入框,输入框也不去通知按钮,它们都跟表单这个中介者打交道。

Java 的界面库里有个 ButtonGroup,一组单选按钮谁被选中,不靠按钮互相通知,都交给按钮组记着,由它去取消别人,也是这个路子。

再往上,网关这类挡在前面的聚合层,干的也是同一件事:前端不直接调十个服务,只调一个入口。不过那已经是跨服务、跨机器的架构,不属于《设计模式》那本书里说的对象协作,这层边界要分清。

04 什么时候别用

两三个对象,交互就一两条,直接调。

你楼下就一个小卖部,买瓶水还要先建个前台,不值。

只有单向的时候,也别用中介者------那是外观(Day⑨)。

别让前台长出业务判断。前台可以记"电工说总闸跳了",不能替业主决定这根线该不该换、钱谁出。中介者一旦开始算钱、判优先级、补规则,它就从转达变成了第二个业务层,以后谁都不敢动。

还有,别把中介者当消息队列使。要跨机器传、要重发、要存下来、要扛住高峰,那是基础设施的活,不是对象之间转句话。

05 灯亮了

下班回家,楼道灯已经亮了。

进门,玄关的灯不亮。以前肯定又是打前台。这次我打开配电箱看了一眼,管照明的那路闸跳了,推上去。

灯亮了。手上沾了点灰。

很小的一件事。但我记住了。

下次上班,对话框还是要开的,需求也还是要交给它先出一版。这没什么不好,快是真的快。

只是下次告警,我打算先把日志自己 grep 一遍,再贴进去。慢十几分钟,但能知道它是从哪一行开始不对的。

跟前面十九篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把请求做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住,原型管拿哪个现成对象复制,桥接管抽象和实现怎么独立扩展,享元管把能共享的部分只留一份,备忘录管把某个时刻的状态存下来、再原样恢复,生成器管把复杂对象的装配拆成一步一步、最后一次性成型,访问者管对一批稳定的元素做什么操作由递进来的单子说了算,组合管单个和一组长一个样,适配器管两套对不上的接口中间怎么搭一座桥。本篇中介者,管一堆对象互相说话时,怎么都先经过一个中间人。

带走一句话:中介者 = 有事都找中间那个人;他替你转达,不替你做主。

事有人替你跑,判断得你自己来。

楼道的灯,自己推上去的那一下,比等师傅来亮,记得久一点。


参考资料

相关推荐
是2的10次方啊1 天前
设计模式与生活⑲:投影连不上,别急着换投影仪
设计模式
cjz38992 天前
规则树:把一百行if-else拆成一棵树
设计模式
Adellle2 天前
设计模式指南 (Java 版) ㊁
java·开发语言·设计模式
是2的10次方啊2 天前
设计模式与生活⑱:工资还没焐热,账单已经凑成一摞
设计模式
百思不得小李3 天前
一个复杂的动态表单是如何实现的
前端·设计模式
爱你真是太好了3 天前
上下文组成以及如何压缩
设计模式
freejackman3 天前
【工程化】从“单兵作战”到“多引擎架构”:基于门面模式的重构实战
大数据·数据库·hive·sql·测试工具·设计模式·架构
Java成神之路-3 天前
从 if-else 到优雅解耦:策略 / 适配器 / 组合模式全流程实战
设计模式
是2的10次方啊3 天前
设计模式与生活⑰:空腹体检,单子领着人走
设计模式