设计原则回顾:六大原则的实战心法

做架构设计这么多年,我越来越觉得——设计原则这东西,不是背出来的,是踩坑踩出来的。今天咱们把六大原则再过一遍,我不讲教科书上的定义,咱们聊聊我在项目中是怎么理解它们的。

一、单一职责原则(SRP)

说白了就是一个类只干一件事。听起来简单,但我在代码评审里见过太多「万能类」了。

核心思想:一个类应该只有一个引起它变化的原因。

举个例子,我以前接手过一个订单处理类,里面混了订单校验、库存扣减、物流通知、用户积分计算……好家伙,一个类干了四个人的活。后来需求一变,改物流逻辑的时候,不小心把积分计算搞崩了。

怎么判断是否违反? 你问自己一个问题:这个类改动的理由是不是超过一个?如果是,就该拆了。

// 违反SRP:一个类处理订单、库存、物流
public class OrderService {
    public void processOrder(Order order) {
        validateOrder(order);
        deductStock(order);
        notifyLogistics(order);
        updateUserPoints(order);
    }
}

// 符合SRP:每个类只负责一件事
public class OrderValidator { ... }
public class InventoryService { ... }
public class LogisticsNotifier { ... }
public class UserPointsService { ... }

我的习惯:写代码前先画个职责边界图,每个类只放一个核心职责。如果发现一个类里出现了「并且」「同时」这类词,就该拆了。

二、开闭原则(OCP)

对扩展开放,对修改关闭。这话听着抽象,其实核心就一句:加新功能的时候,尽量别改老代码。

我记得有一次做支付系统,一开始只支持微信支付。后来要加支付宝,如果直接在老代码里加if-else,那每次加新支付方式都得改一遍。后来我改成策略模式,每种支付方式一个类,新增支付方式只需要加新类,老代码动都不动。

// 违反OCP:每次加新支付方式都要改这里
public class PaymentService {
    public void pay(String type) {
        if ("wechat".equals(type)) { ... }
        else if ("alipay".equals(type)) { ... }
    }
}

// 符合OCP:扩展新支付方式只需加新类
public interface PaymentStrategy {
    void pay(Order order);
}

public class WechatPay implements PaymentStrategy { ... }
public class AlipayPay implements PaymentStrategy { ... }

避坑指南:我曾经在一个项目里过度追求开闭原则,把简单逻辑硬拆成十几个接口和实现类,结果代码反而更难维护了。记住,开闭原则不是让你把所有东西都抽象化,而是要在「变化点」上做抽象。

三、里氏替换原则(LSP)

子类应该能替换父类,而且程序行为不变。说白了就是:你说是鸟,就得会飞;你说是鱼,就得会游。别搞个「企鹅」硬说它是鸟,结果飞不起来。

我在项目中见过一个经典的反例:有个矩形类,有宽和高。后来想复用代码,让正方形继承矩形。结果正方形的setWidth方法把高度也改了,导致所有依赖矩形的地方都出了问题。

// 违反LSP:正方形不能完全替代矩形
public class Rectangle {
    protected int width, height;
    public void setWidth(int w) { this.width = w; }
    public void setHeight(int h) { this.height = h; }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w);  // 强行保持正方形,但破坏了父类行为
    }
}

// 正确做法:让矩形和正方形都继承自「形状」抽象类

我的判断方法:写继承前先问自己——子类能不能完全替代父类?如果不能,就别用继承,用组合。

四、依赖倒置原则(DIP)

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

这句话有点绕,我换个说法:你的业务逻辑代码,不要直接依赖具体的数据库、具体的第三方服务。要依赖接口。这样换数据库、换服务的时候,业务代码不用动。

// 违反DIP:业务层直接依赖MySQL
public class UserService {
    private MySQLUserRepository repo = new MySQLUserRepository();
}

// 符合DIP:业务层依赖接口
public class UserService {
    private UserRepository repo;  // 依赖抽象
    public UserService(UserRepository repo) {
        this.repo = repo;  // 具体实现由外部注入
    }
}

实战经验:我习惯在项目初期就把核心业务接口定义好,具体实现放到后面再写。这样即使底层技术方案没定,业务代码也能先写起来。

五、接口隔离原则(ISP)

客户端不应该依赖它不需要的接口。说白了就是:接口别设计得太胖,要瘦身。

我见过一个「万能接口」,里面塞了增删改查、导出报表、发送通知、生成日志……结果每个实现类都得实现一堆用不到的方法,要么空实现,要么抛异常。

// 违反ISP:接口太胖
public interface Worker {
    void work();
    void eat();
    void sleep();
    void manage();
}

// 符合ISP:拆成多个小接口
public interface Workable { void work(); }
public interface Eatable { void eat(); }
public interface Manageable { void manage(); }

我曾经踩过的坑:一开始觉得接口拆太细麻烦,就搞了个大接口。结果后来每个新需求都要改这个接口,所有实现类都得跟着改。那叫一个酸爽。现在我的原则是:接口方法不超过5个,超过就考虑拆分。

六、迪米特法则(LoD)

也叫最少知识原则。一个对象应该对其他对象有最少的了解。不要和陌生人说话。

举个例子,你让下属去办事,你只需要告诉他「去把这件事办了」,不需要知道他是怎么坐车、怎么找人、怎么签字的。代码里也一样,A对象调用B对象的方法,不要通过B再去拿C对象的方法。

// 违反迪米特法则:A直接操作了C
public class A {
    public void doSomething(B b) {
        C c = b.getC();
        c.doSomething();  // A和C产生了耦合
    }
}

// 符合迪米特法则:B封装好操作,A只和B打交道
public class A {
    public void doSomething(B b) {
        b.doSomething();  // A只依赖B
    }
}

我的习惯:写代码时如果发现一个方法里出现了「点链」——比如 a.getB().getC().doSomething()——我就知道该重构了。

知识体系总览

下面这张图把六大原则的核心关系画出来了,方便你整体把握:

六大设计原则知识体系 设计原则 单一职责原则 (SRP) 开闭原则 (OCP) 里氏替换原则 (LSP) 依赖倒置原则 (DIP) 接口隔离原则 (ISP) 迪米特法则 (LoD) 六大原则不是孤立存在的,它们相互补充、相互支撑 SRP是基础,OCP是目标,LSP是保障,DIP是手段,ISP是细化,LoD是约束 实际项目中,通常需要综合运用多个原则来解决复杂问题

六大原则的实战关系

这些原则不是孤立的。我举个例子你就明白了:

假设你要设计一个订单处理系统。SRP告诉你订单校验、库存扣减、支付处理要分开。OCP告诉你加新支付方式时不要改老代码。LSP告诉你子类要能替代父类。DIP告诉你业务层依赖接口而不是具体实现。ISP告诉你支付接口别塞太多方法。LoD告诉你订单处理类不要直接操作第三方支付网关的内部对象。

你看,一个功能设计,六个原则全用上了。

原则 核心问题 一句话总结
SRP 一个类该做多少事? 一个类只干一件事
OCP 如何应对需求变化? 加功能不改老代码
LSP 继承关系是否合理? 子类要能替代父类
DIP 依赖关系怎么设计? 面向接口编程
ISP 接口该多大? 接口要瘦身
LoD 对象间该知道多少? 不和陌生人说话

最后说两句:设计原则是指导方针,不是法律条文。我见过太多人为了遵守原则而过度设计,反而把代码搞复杂了。记住,原则是为可维护性服务的,如果某个原则让你的代码更难维护,那一定是理解出了问题。

嗯,这六个原则,说白了就是让你写出来的代码好改、好扩展、好维护。下次写代码前,拿这六条过一遍,能帮你少走不少弯路。

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