设计模式与生活⑥:问题一个个砸过来,先写张便签

周四早上出门,太阳已经升起来了。

连着阴了几天,一抬头,天是亮的,连路边那排共享单车都被照得发白。小王眯了一下眼,昨晚那点闷,散了一半。

昨晚弄到很晚。到家过了十一点,洗漱完躺下,已经过了零点。睡着以后还在排查问题,梦里那个报错一直在响,怎么都关不掉。

到公司坐稳,电脑刚亮起来,消息就跟排队似的往外冒:群里三条 @,私聊两条,工位隔板上还贴着一张不知道谁留的便利贴,上面写着"帮忙看下这个,急"。都急。

一个早上,六件事,件件都说自己急

他以前的做法是:哪条最新看哪条,谁催得紧先回谁。一个上午过去,最急的那件还没动,最不着急的倒被他翻来覆去看了三遍。

中间查 A 的时候发现是 B 引起的,查 B 又牵出 C 的配置不对。一个接一个,一环套一环,脑子里那根线,越扯越乱。

不是不够努力,是"要做的事"全堆在脑子里,没有一件能被拿起来。

今天聊的就是把它拿出来的写法:命令(Command)------把"要做什么"写成一个对象,于是它能排队、能排序、能撤销、能留档。系列第六篇。

01 别把事堆在脑子里

先看一个不太聪明的写法。三个求助砸过来,最直接的写法就是在流程里挨个调:

java 复制代码
// 三个求助,直接调
restartService("pay-service");
clearCache("item-detail");
setConfig("pay-switch", "on");

三行下去,事就过去了,什么都没留下

问题不在于这三行写得不对,在于它们只存在于执行的那一瞬间。谁提的、几点要、做到哪一步、做错了怎么退,全在脑子里。

脑子还扛得住三件的时候,这套写法看不出毛病。扛到六件就开始丢------昨晚他就是这么丢的:查 A 查到 B,查 B 又牵出 C,回头发现最早那条催得最急的消息,还躺在聊天列表里没点开。

丢的不是记性。是这些事本身,没有一件能被单独拿起来。

没有被写成对象的事,只能活在记忆里。

02 一张便签,三行字

命令模式的第一步很简单:把"要做什么"写成对象。

一张便签上其实就三样东西:干什么、做错了怎么退,还有顶上那行抬头------一眼能认出是哪件事。

java 复制代码
interface Ticket {              // 一张便签
    void execute();             // 干这件事
    void undo();                // 退回去
    String summary();           // 抬头:一眼认出哪件
}

// 改配置:老值记下来,undo 才退得回去
class ConfigChange implements Ticket {
    private final String key, from, to;
    ConfigChange(String key, String from, String to) {
        this.key = key; this.from = from; this.to = to;
    }
    public void execute() { setConfig(key, to); }
    public void undo()    { setConfig(key, from); }   // 有旧值,就能退
    public String summary() { return "改 " + key + ":" + from + " → " + to; }
}

// 重启服务:没有反向操作
class RestartService implements Ticket {
    private final String service;
    RestartService(String service) { this.service = service; }
    public void execute() { restart(service); }
    public void undo()    { }                     // 重启退不回去,这里只能空着
    public String summary() { return "重启 " + service; }
}

同一张便签的形状,退不回去的那张只能空着

关键不是"封装成类"这四个字。是这张便签可以离开你的脑子单独存在:能递给别人,能排进一列,能贴在那儿先不办。

03 便签墙:贴、挑、记

便签写好了,接下来得有个地方贴。

java 复制代码
// 便签墙:只管排序、挑一张、记账
class Board {
    private final List<Ticket> todo = new ArrayList<>();
    private final Deque<Ticket> done = new ArrayDeque<>();

    void add(Ticket t) { todo.add(t); }        // 新便签贴到队尾,不打断手上这张

    void runNext() {
        Ticket t = todo.remove(0);             // 挑最前面那张
        t.execute();
        done.push(t);                          // 做完记一笔
    }

    void undoLast() {
        if (!done.isEmpty()) done.pop().undo();   // 上一步错了,退回去
    }
}

便签墙不认识"重启"和"改配置"。它不知道重启要多久、缓存怎么刷,它只做三件事:贴、挑、记。

反过来,便签也不认识墙。ConfigChange 不需要知道自己是第几张被执行的。两边只通过 Ticket 这一个接口说话。

以后要加"回滚发布""扩一台机器",只是多写一个 Ticket 实现,墙一行都不用改。

调用者只管排队,执行者只管干活,中间隔着一张便签。

一张便签,把三方隔开

上午那根线,现在能理顺了。

上午刚开工,测试在群里发截图,下单报错。他翻日志,看见 pay-switch 这个配置昨晚发布时被关掉了。改回来,再重启支付服务。

还没坐稳,产品又来一条:商品详情页的价格不对。查下去是同一条线------昨晚那批配置改动上线后,商品缓存没刷。清一下缓存,价格就回去了。

三张便签,两个根因。

回头复盘,这三张便签的理想顺序是:先改配置,再清缓存,最后才重启。顺序不是拍脑袋定的,是"能不能退"定的------重启退不回去,就放到最后。

先做退得回去的,最后做退不回去的

这条规矩写在便签的 undo 里,比记在脑子里可靠得多。

04 最朴素的命令,你天天在写

这套东西不新鲜,JDK 里天天见:

java 复制代码
executor.submit(() -> restart("pay-service"));

一张没写抬头、也没有 undo 的便签

Runnable 就是最朴素的便签:只有一个 execute(run),没有 undo,顶上也没有那行抬头。submit 是把便签贴到墙上,线程池就是那个一张一张把它们做掉的人。

再往身边看,形状全一样:

  • 界面上的按钮,点一下,就是执行一张便签;
  • 编辑器的 Ctrl+Z,按一下,就是从"做过的事"那一列弹出一张,调它的 undo;
  • 数据库能回滚,靠的是日志里记着每一笔改动怎么退回去。

它们都在做同一件事:把"要做的事"打包成一个能传递的东西。

05 什么时候,别乱写便签

便签不是越多越好。

只有一次、不排队、不撤销、也不用留档的调用,直接调方法就行。把每个方法都包成命令,本来一眼能看明白的代码,现在得跳三层。

undo 也不是白送的。得自己写反向操作,而且很多事根本没有反向:重启退不回去,发出去的短信撤不回,扣掉的钱要退得走另一套流程。便签撕掉是免费的,代码里往回退一步,是要还的。

还有一件事便签不管:轻重缓急。

便签墙上先挑哪张,是排序的事,排序规则本身还能是个策略(第四篇那个)。命令管"这件事怎么被拿起来",不管"先办哪件"。这两件事混在一起写,最后一定是两者都做不好。

这两年排查问题,AI 给方向确实快。它甚至能直接把这三张便签的代码写好。

但有两件事它替不了:一是这张便签到底该不该执行,二是执行完了退不回来怎么办。方向对了省一小时,方向错了,你得有本事退回来。

命令模式不替你做决定,它只让你做的决定能被拿起来。

06 收工

下班时天已经擦黑。便签墙上一列划掉,一列还空着。

他头一回在下班前,把今天干了什么一口气说完:六件事,追下去只有两个根因。

昨晚梦里那个一直响的报错,今天算是关掉了。

跟前面五篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把"要做的事"本身做成一个东西。

带走一句话:命令 = 把"要做的事"变成一个能排队、能撤销、能留档的对象。

能写下来的事,才轮得到你安排。


参考资料

相关推荐
是2的10次方啊1 天前
设计模式与生活⑤:天冷了,给电动车套件挡风被
设计模式
昭昭日月明2 天前
AI 时代的《重构》
设计模式·架构
工一木子2 天前
设计模式与生活③:你的早晨,是一套模板
设计模式
是2的10次方啊2 天前
设计模式与生活④:冰箱空了,早餐换个买法
设计模式
知守观3 天前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式
多弗朗皮卡丘3 天前
C++特殊类设计
开发语言·c++·设计模式
Carl_奕然3 天前
【智能体】Loop 的四种设计模式之:Verification Loop(2026 最新版)
人工智能·python·设计模式
是2的10次方啊3 天前
设计模式与生活③:你的早晨,是一套模板
设计模式
.Hypocritical.4 天前
JUC + 三大设计模式(单例、工厂、代理)
设计模式·juc·1024程序员节