设计模式与维护性:责任链、中介者、备忘录

做软件这行,最怕什么?我最怕改需求。不是怕改代码本身,而是怕牵一发而动全身。你想想看,一个功能改了,结果三个模块跟着崩,那感觉就像拆炸弹剪错了线。

今天聊的三个模式,说白了就是帮我们解决维护性的痛点。责任链让你能轻松增删处理逻辑,中介者帮你砍掉模块间的直接依赖,备忘录则给了你后悔药——支持回滚。我在好几个项目中都吃过耦合的亏,后来发现,用好这三个模式,代码的“抗揍能力”能提升一大截。

责任链模式:增删处理,就像搭积木

先说说责任链。这个模式的核心思想很简单:把多个处理对象串成一条链,请求沿着链传递,直到某个对象处理它为止。

我举个例子。你写了一个审批系统,普通员工请假1天,组长批;3天,经理批;5天以上,总监批。如果用if-else写,代码会变成这样:

// 糟糕的写法
public void approve(int days) {
    if (days <= 1) {
        // 组长审批
    } else if (days <= 3) {
        // 经理审批
    } else {
        // 总监审批
    }
}

问题在哪?哪天要加个“副总裁审批7天以上”,你得改这个if-else。哪天要调整审批层级,你还得改。这代码就像一堵墙,改一块就得动整面。

用责任链模式重构一下:

// 抽象处理者
abstract class Approver {
    protected Approver next;
    
    public void setNext(Approver next) {
        this.next = next;
    }
    
    public abstract void handle(int days);
}

// 具体处理者
class TeamLeader extends Approver {
    public void handle(int days) {
        if (days <= 1) {
            System.out.println("组长审批通过");
        } else if (next != null) {
            next.handle(days);
        }
    }
}

class Manager extends Approver {
    public void handle(int days) {
        if (days <= 3) {
            System.out.println("经理审批通过");
        } else if (next != null) {
            next.handle(days);
        }
    }
}

// 使用
Approver leader = new TeamLeader();
Approver manager = new Manager();
leader.setNext(manager);
leader.handle(2);  // 经理审批

现在想加个副总裁?简单,写个新类,挂到链上就行。想调整顺序?改一下setNext的调用。每个处理者只关心自己能处理的事,处理不了就往后传。这就是责任链的魅力——增删处理逻辑,就像搭积木一样轻松。

核心要点:责任链模式把请求的发送者和接收者解耦。每个处理者只知道自己下一个是谁,不需要知道整个链的结构。这样,增删处理节点就不会影响其他节点。

我的经验:我在做日志框架时用过责任链。不同级别的日志(DEBUG、INFO、ERROR)需要不同的输出方式。用责任链,我可以自由组合:控制台输出、文件输出、远程输出。想加个数据库输出?写个新处理器,挂上去就行。

中介者模式:砍掉依赖,让模块独立

中介者模式,说白了就是“别直接找我,找中间人”。

你想想看,一个聊天室里,如果每个人都要直接跟其他人通信,那得维护多少连接?10个人,每个人要维护9个连接,总共45个连接。这还只是聊天室,要是换成复杂的业务系统,模块间的依赖关系能让你头皮发麻。

我曾经维护过一个电商系统,订单模块、库存模块、支付模块、物流模块……它们之间互相调用,代码里到处都是new和依赖注入。改一个模块,至少影响三个模块。那段时间,我每天上班第一件事就是祈祷别出bug。

用中介者模式改造后,所有模块只跟中介者通信:

// 中介者接口
interface Mediator {
    void notify(Object sender, String event);
}

// 具体中介者
class OrderMediator implements Mediator {
    private Inventory inventory;
    private Payment payment;
    private Logistics logistics;
    
    public void notify(Object sender, String event) {
        if (sender instanceof Order && event.equals("create")) {
            inventory.reduce();
            payment.charge();
            logistics.ship();
        }
    }
}

// 模块只依赖中介者
class Order {
    private Mediator mediator;
    
    public void create() {
        mediator.notify(this, "create");
    }
}

现在,订单模块不需要知道库存怎么减、支付怎么扣、物流怎么发。它只需要告诉中介者:“我创建了一个订单。”剩下的,中介者去协调。哪天要加个“优惠券模块”?改中介者就行,订单模块不用动。

核心要点:中介者模式将多对多的交互简化为一对多的交互。所有模块只依赖中介者,不依赖彼此。这样,任何一个模块的变化都不会直接影响到其他模块。

避坑指南:我曾经犯过一个错误——把中介者写成“上帝对象”。所有逻辑都塞进中介者,结果中介者变得臃肿不堪,比原来的直接耦合还难维护。记住,中介者只负责协调,不负责具体业务逻辑。具体逻辑还是放在各个模块里。

备忘录模式:后悔药,支持回滚

备忘录模式,就是给对象拍个快照。哪天想回到过去,把快照恢复就行。

这个模式在游戏里用得最多。你玩游戏,打Boss前存个档,打不过就读档重来。在软件里,文本编辑器的撤销功能、数据库的事务回滚,本质上都是备忘录模式。

我做过一个配置管理系统,用户修改配置后可以保存,也可以撤销。用备忘录模式实现:

// 备忘录
class Memento {
    private String state;
    
    public Memento(String state) {
        this.state = state;
    }
    
    public String getState() {
        return state;
    }
}

// 发起人
class Config {
    private String content;
    
    public void setContent(String content) {
        this.content = content;
    }
    
    public Memento save() {
        return new Memento(content);
    }
    
    public void restore(Memento memento) {
        this.content = memento.getState();
    }
}

// 管理者
class History {
    private Stack<Memento> stack = new Stack<>();
    
    public void push(Memento memento) {
        stack.push(memento);
    }
    
    public Memento pop() {
        return stack.pop();
    }
}

// 使用
Config config = new Config();
History history = new History();

config.setContent("版本1");
history.push(config.save());

config.setContent("版本2");
// 想回退?
config.restore(history.pop());  // 回到版本1

注意,备忘录模式有三个角色:发起人(要保存状态的对象)、备忘录(保存的状态快照)、管理者(管理备忘录的存储和恢复)。发起人负责创建和恢复备忘录,管理者负责保存备忘录,但不关心备忘录的内容。

核心要点:备忘录模式在不破坏封装的前提下,捕获并外部化一个对象的内部状态,以便以后恢复。它把状态保存和恢复的责任从业务逻辑中分离出来。

我的习惯:我一般用栈来管理备忘录,这样天然支持“撤销”操作。如果要做“重做”,再加一个栈。另外,备忘录不要保存太多数据,否则内存会爆。我见过一个项目,每次保存都序列化整个对象,结果用户操作几次后,内存就飙到几个G。只保存变化的部分,或者用增量快照,会好很多。

三者对比:什么时候用哪个?

模式 解决的核心问题 适用场景 我的建议
责任链 处理逻辑的增删和顺序调整 审批流程、日志过滤、事件处理 处理节点数量不确定,或者经常变化时用
中介者 模块间的直接依赖 聊天室、MVC中的Controller、复杂交互系统 模块数量多、交互复杂时用,但别让中介者变胖
备忘录 状态回滚和撤销 编辑器撤销、游戏存档、事务回滚 需要支持撤销/重做功能时用,注意内存管理

这三个模式,我建议你组合使用。比如,一个配置管理系统,可以用责任链处理配置的校验逻辑(校验A→校验B→校验C),用中介者协调配置修改后的通知(通知订单模块、通知库存模块),用备忘录支持配置的撤销和回滚。三个模式各司其职,代码的维护性会好很多。

设计模式与维护性:核心逻辑图 责任链模式 中介者模式 备忘录模式 核心价值: 易于增删处理节点 处理逻辑灵活组合 核心价值: 减少模块间直接依赖 交互集中管理 核心价值: 支持状态回滚 不破坏封装性 适用场景 审批流程、日志过滤 聊天室、MVC控制器 编辑器撤销、游戏存档 三个模式可组合使用,提升系统维护性

最后说一句,设计模式不是银弹。别为了用模式而用模式。我见过有人在一个只有三个类的系统里硬套责任链,结果代码比不用还复杂。模式是工具,不是目的。当你的代码出现“改一处动全身”的痛点时,再考虑用这些模式来解耦。那时候,你会觉得它们真香。

公众号:蓝海资料掘金营,微信deep3321