其他重要设计原则:从“知道”到“做到”
设计模式是招式,设计原则是心法。今天我们来聊聊那些容易被忽略,但实际项目中天天用到的原则。我个人习惯把这些原则称为“代码的卫生习惯”——不遵守不会立刻出问题,但时间长了代码就会“生病”。
迪米特法则(LoD):别和陌生人说话
迪米特法则,也叫最少知识原则。说白了就是:一个对象应该对其他对象有尽可能少的了解。
我在项目中遇到过这样的场景:一个订单类直接调用了用户类的地址类的城市字段。看起来没问题,但后来地址结构改了,订单类也得跟着改。这就是典型的“管得太宽”。
核心思想:只与你的直接朋友通信,不要跟“朋友的朋友”说话。
怎么判断是不是“直接朋友”?
- 当前对象本身(this)
- 作为参数传入的对象
- 当前对象创建或实例化的对象
- 当前对象的成员变量
// ❌ 违反迪米特法则
public class Order {
public void process() {
Address addr = customer.getAddress();
String city = addr.getCity(); // 跟“朋友的朋友”说话了
}
}
// ✅ 符合迪米特法则
public class Order {
public void process() {
String city = customer.getCity(); // 让朋友自己去做
}
}
我的经验:写代码时多问自己一句“这个对象真的需要知道这些细节吗?”往往能避免很多后续的修改。
组合/聚合复用原则(CARP):少继承,多组合
这个原则我特别喜欢。它告诉我们:优先使用组合或聚合,而不是继承来达到复用目的。
为什么?继承是白箱复用,子类能看到父类的实现细节,耦合度高。组合是黑箱复用,通过接口交互,耦合度低。
// ❌ 继承导致的问题
public class Stack<E> extends Vector<E> {
// 继承了Vector的所有方法,包括不该暴露的
}
// ✅ 组合更安全
public class Stack<E> {
private Vector<E> vector = new Vector<>();
public void push(E e) {
vector.add(e);
}
public E pop() {
// 只暴露需要的操作
}
}
我曾经踩过的坑:用继承实现了一个“按钮”和“带图标的按钮”的关系。后来需求变成“按钮可以没有图标,但图标可以单独存在”。整个继承体系全乱了。改成组合后,问题迎刃而解。
好莱坞原则:别打电话,我们会打给你
这个原则的名字很有意思。它说的是:高层组件决定什么时候调用低层组件,而不是反过来。
你想想看,在框架设计中,框架是“导演”,你的代码是“演员”。导演说“开始”你才能演,你不能自己抢戏。这就是控制反转(IoC)的核心。
| 场景 | 违反好莱坞原则 | 符合好莱坞原则 |
|---|---|---|
| 事件处理 | 轮询检查事件 | 注册回调,由框架通知 |
| 依赖注入 | 自己new依赖对象 | 由容器注入依赖 |
| 模板方法 | 子类决定流程 | 父类定义骨架,子类填空 |
实际应用:Spring框架就是好莱坞原则的典型代表。你的Bean不需要主动去找依赖,容器会在合适的时机注入。
KISS原则:保持简单,保持愚蠢
KISS原则是我最常挂在嘴边的。不是开发者不够聪明,而是复杂的代码往往意味着更高的维护成本。
我记得有一次review代码,看到一个方法写了200行,里面嵌套了5层if-else。我问作者:“这个逻辑真的需要这么复杂吗?”他想了半天,最后用两个简单的策略模式就搞定了。
怎么判断代码是否“简单”?
- 一个方法能不能在30秒内看懂?
- 新人接手需要多久才能修改?
- 测试用例好不好写?
// ❌ 过度设计
public class StringProcessor {
private static final StringProcessor INSTANCE =
new StringProcessor();
private final List<StringTransformer> transformers;
// ... 工厂模式、策略模式、观察者模式全用上了
}
// ✅ KISS版本
public class StringProcessor {
public static String trimAndLower(String input) {
return input.trim().toLowerCase();
}
}
注意:KISS不是让你不用设计模式。而是说,当简单方案够用时,别为了用模式而用模式。
YAGNI原则:你不会需要它的
YAGNI是“You Ain't Gonna Need It”的缩写。它告诉我们:只实现当前需要的功能,不要为未来可能的需求提前设计。
我见过太多项目死在“过度设计”上。团队花了两周时间设计一个“万能配置系统”,结果项目上线后只用到了最简单的key-value配置。那两周的时间,如果用来做核心业务,产品可能早就上线了。
核心问题:你怎么知道未来需要什么?大多数时候,你猜不准。
什么时候可以“提前设计”?
- 这个扩展点几乎确定会用到(比如支付方式)
- 现在不加,以后改造成本极高
- 不影响当前功能的交付时间
DRY原则:别重复你自己
DRY原则可能是最广为人知,但也最容易被误解的原则。它不是说“代码不能重复”,而是说“知识不能重复”。
举个例子:业务规则“订单金额超过1000元打9折”出现在三个地方——订单计算、报表统计、优惠券发放。这就是知识重复。如果规则改成“满800打8折”,你得改三个地方,漏一个就出bug。
// ❌ 知识重复
public class OrderService {
public double calculate(double amount) {
if (amount > 1000) return amount * 0.9;
return amount;
}
}
public class ReportService {
public double getDiscount(double amount) {
if (amount > 1000) return amount * 0.9; // 同样的规则
return amount;
}
}
// ✅ 知识集中
public class DiscountPolicy {
public static double apply(double amount) {
if (amount > 1000) return amount * 0.9;
return amount;
}
}
我的判断标准:如果同一个业务逻辑需要同步修改多处,那就是DRY没做好。如果只是代码长得像但含义不同,那重复也没关系。
原则之间的关系
这些原则不是孤立的。它们互相补充,有时候也会冲突。比如DRY和YAGNI就可能打架——为了DRY提前抽象,可能违反了YAGNI。
我个人习惯的做法是:先用KISS和YAGNI快速实现功能,等发现重复代码出现第三次时,再用DRY和CARP去重构。这样既不会过度设计,也不会让代码腐烂。
嗯,这些原则说起来简单,真正用好需要大量实践。我建议你从一个小项目开始,刻意练习每个原则。比如这个模块用迪米特法则重构一下,那个类用组合替代继承。慢慢你就会发现,这些原则已经变成了你的本能反应。
最后说一句:原则是指导,不是教条。理解背后的“为什么”,比死记硬背“是什么”重要得多。
公众号:蓝海资料掘金营,微信deep3321