设计模式与维护性:责任链、中介者、备忘录
做软件这行,最怕什么?我最怕改需求。不是怕改代码本身,而是怕牵一发而动全身。你想想看,一个功能改了,结果三个模块跟着崩,那感觉就像拆炸弹剪错了线。
今天聊的三个模式,说白了就是帮我们解决维护性的痛点。责任链让你能轻松增删处理逻辑,中介者帮你砍掉模块间的直接依赖,备忘录则给了你后悔药——支持回滚。我在好几个项目中都吃过耦合的亏,后来发现,用好这三个模式,代码的“抗揍能力”能提升一大截。
责任链模式:增删处理,就像搭积木
先说说责任链。这个模式的核心思想很简单:把多个处理对象串成一条链,请求沿着链传递,直到某个对象处理它为止。
我举个例子。你写了一个审批系统,普通员工请假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),用中介者协调配置修改后的通知(通知订单模块、通知库存模块),用备忘录支持配置的撤销和回滚。三个模式各司其职,代码的维护性会好很多。
最后说一句,设计模式不是银弹。别为了用模式而用模式。我见过有人在一个只有三个类的系统里硬套责任链,结果代码比不用还复杂。模式是工具,不是目的。当你的代码出现“改一处动全身”的痛点时,再考虑用这些模式来解耦。那时候,你会觉得它们真香。