设计模式六大原则(SOLID)

说到设计模式,我总想起刚入行那会儿。那时候写代码,能跑就行,哪管什么原则不原则。直到有一次,一个同事离职,我接手他的项目——好家伙,一个类三千行,改一个功能要翻遍十几个文件。那感觉,就像在雷区里跳舞。

后来我才明白,设计模式不是花架子。它的根基,就是这六大原则。说白了,它们是写代码的「交通规则」。遵守了,代码就清晰、好改、不容易崩。不遵守,早晚要还债。

SOLID 原则 是面向对象设计的五个基本原则,加上迪米特法则,构成了我们日常编码的「行为准则」。

1. 单一职责原则(SRP)

一个类,只做一件事。听起来简单吧?但我见过太多「万能类」了。

我记得有一次重构一个订单系统。原来的 OrderService 里,既有订单计算,又有数据库操作,还负责发邮件。改一个逻辑,三个地方都要测。我花了整整两天,把它拆成 OrderCalculatorOrderRepositoryEmailNotifier。从那以后,改需求再也不用提心吊胆了。

判断标准:如果你写一个类的职责时,需要用「和」字连接,那就说明它可能违反了单一职责。

// ❌ 违反单一职责
public class OrderService {
    public void createOrder(Order order) {
        // 计算价格
        // 保存到数据库
        // 发送邮件通知
    }
}

// ✅ 符合单一职责
public class OrderCalculator {
    public double calculateTotal(Order order) { ... }
}

public class OrderRepository {
    public void save(Order order) { ... }
}

public class EmailNotifier {
    public void sendConfirmation(Order order) { ... }
}

2. 开闭原则(OCP)

对扩展开放,对修改关闭。这句话我背了无数遍,但真正理解是在一次支付模块重构中。

原来的代码,每加一种支付方式就要改 PaymentService 里的 if-else。后来我改成策略模式,新增支付方式只需要加一个新类,老代码一行不动。你想想看,这多省心。

// ❌ 违反开闭原则
public class PaymentService {
    public void pay(String type) {
        if (type.equals("alipay")) { ... }
        else if (type.equals("wechat")) { ... }
        // 加新支付方式就要改这里
    }
}

// ✅ 符合开闭原则
public interface PaymentStrategy {
    void pay(double amount);
}

public class AlipayStrategy implements PaymentStrategy { ... }
public class WechatStrategy implements PaymentStrategy { ... }
// 新增:public class ApplePayStrategy implements PaymentStrategy { ... }

3. 里氏替换原则(LSP)

子类必须能替换父类,而且程序行为不变。嗯,这里要注意,不是「长得像」就能继承。

我曾经见过一个经典的反例:Square 继承 Rectangle。正方形重写了宽高设置方法,结果导致父类的逻辑全乱了。说白了,继承不是用来「复用代码」的,而是用来表达「is-a」关系的。

避坑指南:如果你发现子类重写父类方法时,需要抛出新的异常,或者改变方法的语义,那很可能违反了里氏替换原则。

// ❌ 违反里氏替换
public class Rectangle {
    protected int width, height;
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        width = w;
        height = w;  // 改变了父类的行为!
    }
}

// ✅ 符合里氏替换:使用抽象接口
public interface Shape {
    int getArea();
}

public class Rectangle implements Shape { ... }
public class Square implements Shape { ... }

4. 接口隔离原则(ISP)

客户端不应该依赖它不需要的接口。说白了,接口要小而专,不要大而全。

我习惯把接口设计成「角色」而非「实体」。比如一个员工,他可能既是「程序员」又是「会议参与者」。那就拆成两个接口,而不是一个「员工接口」包含所有方法。

// ❌ 违反接口隔离
public interface Worker {
    void code();
    void attendMeeting();
    void manageTeam();
}

// ✅ 符合接口隔离
public interface Coder {
    void code();
}

public interface MeetingParticipant {
    void attendMeeting();
}

public interface Manager {
    void manageTeam();
}

5. 依赖倒置原则(DIP)

高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

这句话有点绕,我换个说法:写代码时,尽量面向接口编程,而不是面向具体实现。我在项目中经常用依赖注入来实现这一点。Spring 的 IoC 容器,说白了就是 DIP 的典型实践。

// ❌ 违反依赖倒置
public class NotificationService {
    private EmailSender emailSender = new EmailSender(); // 依赖具体实现
}

// ✅ 符合依赖倒置
public class NotificationService {
    private MessageSender sender;
    
    public NotificationService(MessageSender sender) {
        this.sender = sender; // 依赖抽象
    }
}

public interface MessageSender {
    void send(String message);
}

6. 迪米特法则(LoD)

一个对象应该对其他对象保持最少的了解。也叫「最少知识原则」。

我有个习惯:如果一个方法里出现了连续的点号调用(比如 a.getB().getC().doSomething()),我就会警觉。这通常意味着耦合太深了。

实战技巧:迪米特法则不是让你完全不调用其他对象的方法,而是让你只和「直接的朋友」通信。直接的朋友包括:当前对象本身、作为参数传入的对象、成员变量引用的对象、方法内创建的对象。

// ❌ 违反迪米特法则
public class Customer {
    public void buyItem(Store store) {
        store.getInventory().getItem("book").purchase(); // 和陌生人通信
    }
}

// ✅ 符合迪米特法则
public class Customer {
    public void buyItem(Store store) {
        store.purchaseItem("book"); // 只和直接朋友通信
    }
}

六大原则的关系

这六个原则不是孤立的。它们互相配合,共同构成了设计模式的基石。我画了一张图,帮你理清它们的关系:

SOLID + 迪米特法则 知识体系 设计模式六大原则 单一职责 (SRP) 一个类只做一件事 开闭原则 (OCP) 对扩展开放,对修改关闭 里氏替换 (LSP) 子类可替换父类 接口隔离 (ISP) 接口小而专 依赖倒置 (DIP) 面向接口编程 迪米特法则 (LoD) 最少知识原则 前三个原则(SRP、OCP、LSP)关注类的设计 后三个原则(ISP、DIP、LoD)关注类之间的关系

我的实践建议

学了这么多原则,你可能会问:实际项目中怎么用?我个人的习惯是这样的:

  • 写代码前:先想想这个类的职责是否单一。如果感觉「这个类好像要做很多事」,那就拆。
  • 写代码时:多用接口和抽象类。面向接口编程,而不是面向实现。
  • 写代码后:检查一下有没有违反里氏替换。子类能不能无缝替换父类?如果不能,说明继承关系有问题。
  • 重构时:看看有没有「万能类」或「长方法」。有的话,按单一职责拆开。

注意:原则是指导,不是教条。有时候为了性能或简化设计,适当违反某个原则是可以接受的。但你要清楚自己在做什么,并且承担后果。

好了,这就是设计模式的六大原则。它们就像写代码的「内功心法」。招式可以变,但内功不能丢。希望你在实际项目中,能慢慢体会到它们的价值。