一、为什么你必须先懂「原则」,再谈「模式」?
很多小伙伴一上来就背 23 种设计模式,结果越学越晕。
其实 所有设计模式都围着七大原则转——把它们吃透,模式只是信手拈来的招式。
二、七大原则,一条一条拆给你看!
① 单一职责原则(SRP)
一句话:一个类只做一件事。
颜色加粗重点:如果类名里出现 “And”、“Or”,八成已经违背 SRP。
修改前(反面教材)
class UserServiceAndReport {
void register() { /* 注册逻辑 */ }
void exportPDF() { /* 导出报表逻辑 */ }
}
修改后(符合 SRP)
// 只负责用户注册
class UserService {
void register(User user) { /* 注册逻辑 */ }
}
// 只负责报表导出
class ReportService {
void exportPDF(User user) { /* 导出报表逻辑 */ }
}
注解:
把 注册 和 报表 拆到两个类,职责单一。
修改注册逻辑时,不会误伤报表功能,风险降低。
单元测试粒度更细,定位 bug 事半功倍。
② 开闭原则(OCP)
一句话:对扩展开放,对修改关闭。
颜色加粗重点:新增功能靠 “加代码”,而不是 “改旧代码”。
场景:订单折扣系统
// 抽象策略
interface DiscountStrategy {
BigDecimal apply(BigDecimal price);
}
// 新需求:双十一折扣
class DoubleElevenDiscount implements DiscountStrategy {
public BigDecimal apply(BigDecimal price) {
return price.multiply(new BigDecimal("0.8"));
}
}
// 使用方
class OrderService {
BigDecimal pay(BigDecimal price, DiscountStrategy strategy) {
return strategy.apply(price);
}
}
注解:
新增
DoubleElevenDiscount
时,完全不动OrderService
。所有折扣策略实现同一接口,扩展点清晰。
遵循 OCP,减少回归测试成本。
③ 里氏替换原则(LSP)
一句话:子类必须能无缝替换父类。
颜色加粗重点:别把正方形硬塞进长方形的模子。
// 定义接口
interface Shape {
int area();
}
// 长方形
class Rectangle implements Shape {
int width, height;
public int area() { return width * height; }
}
// 正方形(保证 LSP)
class Square implements Shape {
int side;
public int area() { return side * side; }
}
注解:
正方形 不继承长方形,避免 setWidth 时破坏高度。
只实现
Shape
接口,任何 Shape 都能被统一调用。满足 LSP,消除运行时类型检查的尴尬。
④ 接口隔离原则(ISP)
一句话:客户端不依赖它用不到的方法。
颜色加粗重点:胖接口 = 胖 bug 源。
拆分前
interface Machine {
void print();
void scan();
void fax();
}
拆分后
interface Printer { void print(); }
interface Scanner { void scan(); }
interface Fax { void fax(); }
class AllInOne implements Printer, Scanner, Fax {
/* 真正实现 */
}
注解:
老式打印机只实现
Printer
,不必空实现 scan/fax。接口粒度变小,降低实现类的心智负担。
遵循 ISP,提高代码可维护性。
⑤ 依赖倒置原则(DIP)
一句话:高层模块不依赖低层,二者都依赖抽象。
颜色加粗重点:面向接口编程,而不是面向实现。
示例:通知系统
// 抽象
interface Notifier {
void send(String msg);
}
// 低层实现
class EmailNotifier implements Notifier {
public void send(String msg) { /* 发邮件 */ }
}
// 高层业务
class OrderService {
private final Notifier notifier; // 依赖抽象
OrderService(Notifier n) { this.notifier = n; }
void finishOrder() { notifier.send("订单完成"); }
}
注解:
OrderService
只认Notifier
,不关心是邮件还是短信。想换短信通知?只需新增
SmsNotifier
,零修改高层。完美体现 DIP,系统松耦合。
⑥ 迪米特法则(LoD)
一句话:只和直接朋友说话。
颜色加粗重点:链式调用 a.getB().getC().do()
是大忌。
反例
class Wallet {
public Money money;
}
class Person {
public Wallet wallet;
}
// 客户端
person.getWallet().money.reduce(100);
正例
class Person {
private Wallet wallet;
public void pay(int amount) {
wallet.pay(amount);
}
}
// 客户端
person.pay(100);
注解:
客户端只认识
Person
,完全不知道 Wallet 的存在。降低耦合,类内部变化不影响外部调用。
遵循 LoD,代码更易读、更安全。
⑦ 合成复用原则(CRP)
一句话:优先组合,而非继承。
颜色加粗重点:继承是“亲儿子”,组合是“干儿子”,后者更灵活。
继承 VS 组合
// 继承 —— 紧耦合
class Bird extends Flyable { }
// 组合 —— 松耦合
class Bird {
private FlyBehavior flyBehavior; // 组合
void fly() { flyBehavior.fly(); }
}
注解:
组合让
Bird
在运行时动态切换飞行策略。不会陷入“类爆炸”的继承泥潭。
体现 CRP,系统扩展性 max。
总结
单一职责 -> 拆!
开闭原则 -> 加!
里氏替换 -> 换!
接口隔离 -> 瘦!
依赖倒置 -> 抽象!
迪米特 -> 少说话!
合成复用 -> 组合!
背口诀:“拆加换瘦抽象少说话,组合优先”