软件设计七大原则

软件设计七大原则

作为 Java 架构师,我深耕企业级应用设计、微服务架构、分布式系统研发十余年,见过太多项目从「简洁优雅」走向「臃肿腐化」:需求迭代时改一处代码牵一发而动全身,测试成本指数级上升,新人接手代码寸步难行,最终不得不重构甚至推倒重来。根源只有一个:违背了软件设计的七大核心原则

软件设计七大原则是面向对象设计(OOD)的基石,是全球架构师总结的「代码设计黄金准则」,也是SOLID 原则 + 迪米特法则 + 合成复用原则的统称:

  1. 单一职责原则(SRP)
  2. 开闭原则(OCP)
  3. 里氏替换原则(LSP)
  4. 接口隔离原则(ISP)
  5. 依赖倒置原则(DIP)
  6. 迪米特法则(LOD / 最少知道原则)
  7. 合成复用原则(CRP)

这七大原则由浅入深、层层递进:单一职责是基础粒度规范,开闭原则是核心设计目标,里氏替换规范继承,接口隔离规范接口,依赖倒置规范依赖关系,迪米特法则控制耦合,合成复用规范代码复用。

本文全程用通俗语言 + Java 实战代码 + 架构落地场景拆解,拒绝晦涩学术定义,让新手能看懂、老手能落地,总字数超 12000 字,彻底吃透七大原则。


前置铺垫:为什么要学七大原则?

先问一个架构师核心问题:什么是好的软件设计?

答案只有 8 个字:高内聚、低耦合

  • 高内聚:一个模块 / 类只做一件事,内部逻辑紧密关联;
  • 低耦合:模块之间依赖最少,修改一个模块不影响其他模块。

而软件设计七大原则,就是实现「高内聚、低耦合」的唯一方法论

新手写代码:以「实现功能」为目标,能跑就行;

架构师写代码:以「应对变化」为目标,拥抱需求迭代,拒绝代码腐化。

在企业级 Java 项目中(电商、支付、微服务、分布式系统),需求永远在变:新增促销规则、修改支付方式、扩展用户权限、替换中间件... 七大原则就是让你的代码不用改原有逻辑,仅通过扩展就能适配新需求


第一章 单一职责原则(Single Responsibility Principle, SRP)

1.1 通俗定义(由浅入深)

一个类、接口、方法,只负责一项独立的职责,只有一个引起它变化的原因。

用大白话讲:不要让一个类干多份活。就像现实中,医生只看病、老师只教书、厨师只做饭,让一个人同时干三份工作,必然样样做不好。

核心本质

高内聚:将不同的业务逻辑拆分,让每个组件只关注自己的核心职责,从根源上避免代码耦合。

违背原则的致命痛点

  1. 代码臃肿:一个类几千行,阅读、维护难度爆炸;
  2. 复用性差:无法单独复用其中一个功能,必须引入整个冗余类;
  3. 测试困难:一个方法修改,所有关联功能都要重新测试;
  4. 维护灾难:修改一处逻辑,会意外破坏其他无关功能。

1.2 Java 代码实战:反例 vs 正例

我们以用户模块为例,这是 Java 项目最常见的场景。

❌ 反例:违背单一职责(大杂烩类)

一个User类,同时负责用户信息管理、登录认证、支付、日志记录4 个职责,所有逻辑揉在一起:

/**
 * 反例:违背单一职责原则
 * 一个类承担了用户信息、登录、支付、日志4个职责
 */
public class User {
    // 1. 用户信息属性
    private Long userId;
    private String username;
    private String password;

    // 2. 用户信息方法
    public void saveUserInfo() {
        System.out.println("保存用户信息到数据库");
    }

    // 3. 登录认证方法
    public boolean login(String username, String password) {
        System.out.println("用户登录认证");
        return true;
    }

    // 4. 支付方法
    public void pay(double amount) {
        System.out.println("用户支付:" + amount + "元");
    }

    // 5. 日志记录方法
    public void logUserOperation(String operation) {
        System.out.println("记录用户操作日志:" + operation);
    }
}

问题:如果后续要修改支付逻辑,必须改动这个类,可能意外破坏登录、信息保存功能;如果只想复用登录功能,必须引入整个 User 类,冗余代码极多。

✅ 正例:遵循单一职责(拆分职责)

按照单一职责拆分,每个类只负责一项职责:

// 1. 用户信息类:仅负责用户基础信息(职责1)
public class UserInfo {
    private Long userId;
    private String username;
    private String password;

    public void saveUserInfo() {
        System.out.println("保存用户信息到数据库");
    }
}

// 2. 登录服务类:仅负责用户登录认证(职责2)
public class UserLoginService {
    public boolean login(String username, String password) {
        System.out.println("用户登录认证");
        return true;
    }
}

// 3. 支付服务类:仅负责用户支付(职责3)
public class UserPayService {
    public void pay(double amount) {
        System.out.println("用户支付:" + amount + "元");
    }
}

// 4. 日志服务类:仅负责操作日志(职责4)
public class UserLogService {
    public void logUserOperation(String operation) {
        System.out.println("记录用户操作日志:" + operation);
    }
}

方法级别的单一职责

单一职责不仅适用于,也适用于方法

// ❌ 反例:一个方法既查询用户,又修改用户状态(两个职责)
public void getUserAndUpdateStatus(Long userId) {
    // 查询用户
    System.out.println("查询用户信息");
    // 修改状态
    System.out.println("修改用户状态");
}

// ✅ 正例:拆分两个方法,各负其责
public User getUserById(Long userId) {
    System.out.println("查询用户信息");
    return new User();
}

public void updateUserStatus(Long userId, Integer status) {
    System.out.println("修改用户状态");
}

1.3 原则核心要点

  1. 职责划分依据:按业务领域划分(信息、操作、日志、存储、计算);
  2. 粒度控制:不是拆得越细越好,避免「类爆炸」,适度拆分即可;
  3. 适用范围:类、接口、方法、模块、微服务全层级适用。

1.4 架构层面落地

单一职责是微服务拆分的核心依据

  • 电商系统:用户服务、订单服务、支付服务、商品服务(每个服务只做一件事);
  • 后端分层:Controller(接收请求)、Service(业务逻辑)、DAO(数据访问)(分层各司其职)。

第二章 开闭原则(Open Closed Principle, OCP)

2.1 通俗定义(由浅入深)

软件实体(类、模块、方法)对扩展开放,对修改关闭。

用大白话讲:新增需求时,只加新代码,不改老代码。老代码是经过测试验证的稳定代码,修改必然引入 bug 风险。

核心本质

开闭原则是七大原则的核心,其他 6 大原则都是为了实现开闭原则。它的目标是:保护原有代码稳定性,提升系统扩展性

违背原则的致命痛点

  1. 修改老代码,破坏原有功能,回归测试成本极高;
  2. 代码腐化,长期迭代后变成「屎山代码」;
  3. 无法灵活适配新需求,扩展性为 0。

2.2 Java 代码实战:反例 vs 正例

我们以计算器为例,实现加减乘除运算。

❌ 反例:违背开闭原则(修改原有代码)

/**
 * 反例:违背开闭原则
 * 新增运算需要修改原有类,破坏稳定代码
 */
public class Calculator {
    // 运算类型:1加法 2减法
    public int calculate(int a, int b, int type) {
        if (type == 1) {
            return a + b;
        } else if (type == 2) {
            return a - b;
        }
        // 新增乘法,必须修改原有if-else逻辑!
        else if (type == 3) {
            return a * b;
        }
        return 0;
    }
}

问题:新增乘法、除法,必须修改Calculator类的原有代码,一旦改错,加法减法都会失效。

✅ 正例:遵循开闭原则(扩展不修改)

核心思路:抽象出通用接口,新增功能只写实现类,不碰老代码

// 1. 抽象计算接口(固定通用逻辑,不修改)
public interface Calculation {
    int calculate(int a, int b);
}

// 2. 加法实现类(扩展1)
public class AddCalculation implements Calculation {
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
}

// 3. 减法实现类(扩展2)
public class SubCalculation implements Calculation {
    @Override
    public int calculate(int a, int b) {
        return a - b;
    }
}

// 4. 乘法实现类(新增需求:只加新类,不改老代码)
public class MulCalculation implements Calculation {
    @Override
    public int calculate(int a, int b) {
        return a * b;
    }
}

// 5. 计算器类:依赖抽象接口,无需修改
public class Calculator {
    public int compute(Calculation calculation, int a, int b) {
        return calculation.calculate(a, b);
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        // 加法
        System.out.println(calculator.compute(new AddCalculation(), 10, 5));
        // 乘法(新增功能,无任何老代码修改)
        System.out.println(calculator.compute(new MulCalculation(), 10, 5));
    }
}

优势:新增除法、幂运算,只需要新建实现类,完全不触碰原有稳定代码,零风险扩展。

2.3 原则核心要点

  1. 抽象是关键:用接口 / 抽象类定义通用规范,细节由实现类扩展;
  2. 核心逻辑稳定:底层核心代码禁止修改,上层业务通过扩展实现;
  3. 拥抱变化:所有易变的业务逻辑,都抽离为可扩展的实现。

2.4 架构层面落地

  1. Spring 框架:AOP 面向切面编程,不修改业务代码,扩展日志、事务、权限;
  2. 电商促销:新增优惠券、满减、秒杀,只加促销规则实现类,不改订单核心代码;
  3. 插件化开发:IDE 插件、浏览器插件,扩展功能不修改主程序。

第三章 里氏替换原则(Liskov Substitution Principle, LSP)

3.1 通俗定义(由浅入深)

所有引用父类的地方,必须能透明地使用其子类对象,程序逻辑不变。

用大白话讲:子类可以扩展父类功能,但不能破坏父类原有的核心功能。儿子可以比父亲多会技能,但不能把父亲会的本事搞砸。

核心本质

规范继承关系,避免滥用继承,保证多态的正确性。继承是 OOP 三大特性之一,但滥用继承会破坏封装,里氏替换就是继承的「紧箍咒」。

违背原则的致命痛点

  1. 子类替换父类后,程序逻辑出错;
  2. 继承破坏封装,父类的核心逻辑被子类篡改;
  3. 多态失效,系统稳定性崩塌。

3.2 Java 代码实战:反例 vs 正例

经典案例:正方形 vs 长方形(面试高频考点)。

❌ 反例:违背里氏替换原则(正方形继承长方形)

数学上正方形是特殊的长方形,但代码中继承会破坏父类逻辑:

// 父类:长方形
public class Rectangle {
    private int width;
    private int height;

    // setter/getter
    public void setWidth(int width) {
        this.width = width;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getArea() {
        return width * height;
    }
}

// 子类:正方形(继承长方形)
public class Square extends Rectangle {
    // 正方形宽高相等,重写set方法,破坏父类逻辑
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width); // 强制宽高一致
    }

    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

// 测试类:子类替换父类,逻辑出错
public class Test {
    public static void main(String[] args) {
        // 父类引用
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(4);
        rectangle.setHeight(5);
        System.out.println("长方形面积:" + rectangle.getArea()); // 20(正确)

        // 子类替换父类(违背里氏替换)
        rectangle = new Square();
        rectangle.setWidth(4);
        rectangle.setHeight(5); // 预期宽4高5,实际宽高都是5
        System.out.println("正方形面积:" + rectangle.getArea()); // 25(逻辑错误)
    }
}

问题:子类Square重写了父类的set方法,破坏了父类的核心逻辑,替换父类后程序出错。

✅ 正例:遵循里氏替换原则(抽象图形类)

核心思路:抽离抽象父类,正方形和长方形平级继承,互不破坏

// 抽象图形父类(定义通用规范)
public abstract class Shape {
    public abstract int getArea();
}

// 长方形:实现图形类
public class Rectangle extends Shape {
    private int width;
    private int height;

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    @Override
    public int getArea() {
        return width * height;
    }
}

// 正方形:实现图形类(不破坏任何父类逻辑)
public class Square extends Shape {
    private int side;

    public void setSide(int side) { this.side = side; }

    @Override
    public int getArea() {
        return side * side;
    }
}

// 测试类:子类透明替换父类,逻辑正确
public class Test {
    public static void main(String[] args) {
        // 统一用抽象父类引用,子类替换无任何问题
        Shape shape = new Rectangle();
        ((Rectangle) shape).setWidth(4);
        ((Rectangle) shape).setHeight(5);
        System.out.println(shape.getArea()); // 20

        shape = new Square();
        ((Square) shape).setSide(4);
        System.out.println(shape.getArea()); // 16
    }
}

3.3 里氏替换的 4 条核心规则(架构师必背)

  1. 子类必须实现父类的抽象方法,不能拒绝实现;
  2. 子类可以扩展自己的方法,但不能重写父类的非抽象方法
  3. 子类覆写方法时,入参要比父类更宽松,返回值要比父类更严格;
  4. 子类不能抛出父类不支持的异常。

3.4 架构层面落地

  1. 面向抽象编程:方法参数、返回值用父类 / 接口,子类灵活替换;
  2. Spring IOC:容器注入接口,底层实现类随意切换,不影响上层逻辑;
  3. 多态设计:系统底层依赖抽象,上层业务灵活扩展。

第四章 接口隔离原则(Interface Segregation Principle, ISP)

4.1 通俗定义(由浅入深)

客户端不应该依赖它不需要的接口;类间的依赖关系,应该建立在最小的接口上。

用大白话讲:接口要小而专,不要大而全。禁止设计「胖接口」(一个接口包含几十个方法),客户端用不到的方法,被迫空实现,违背设计原则。

核心本质

拆分细粒度接口,降低接口耦合,避免客户端被迫实现无用方法。

与单一职责的区别

  • 单一职责:针对类 / 方法的业务职责,强调「做一件事」;
  • 接口隔离:针对接口的粒度,强调「客户端按需使用」。

违背原则的致命痛点

  1. 胖接口臃肿,维护困难;
  2. 实现类被迫空实现无用方法,代码冗余;
  3. 接口修改,所有实现类都要修改,耦合极高。

4.2 Java 代码实战:反例 vs 正例

我们以动物为例,设计动物的行为接口。

❌ 反例:违背接口隔离(胖接口)

一个Animal接口包含所有行为,狗、鸟、鱼都要实现不需要的方法:

/**
 * 反例:胖接口,包含所有动物行为
 */
public interface Animal {
    void eat();   // 吃
    void sleep(); // 睡
    void fly();   // 飞
    void swim();  // 游
}

// 狗:实现接口,被迫空实现fly()
public class Dog implements Animal {
    @Override
    public void eat() { System.out.println("狗吃骨头"); }
    @Override
    public void sleep() { System.out.println("狗睡觉"); }
    @Override
    public void fly() { } // 无用空实现!
    @Override
    public void swim() { System.out.println("狗会游泳"); }
}

// 鸟:实现接口,被迫空实现swim()
public class Bird implements Animal {
    @Override
    public void eat() { System.out.println("鸟吃虫子"); }
    @Override
    public void sleep() { System.out.println("鸟睡觉"); }
    @Override
    public void fly() { System.out.println("鸟会飞"); }
    @Override
    public void swim() { } // 无用空实现!
}

问题:实现类被迫写无用的空方法,代码冗余,接口耦合极高。

✅ 正例:遵循接口隔离(细粒度小接口)

核心思路:拆分最小粒度接口,客户端按需实现

// 1. 吃接口(最小粒度)
public interface Eatable {
    void eat();
}

// 2. 睡接口(最小粒度)
public interface Sleepable {
    void sleep();
}

// 3. 飞接口(最小粒度)
public interface Flyable {
    void fly();
}

// 4. 游接口(最小粒度)
public interface Swimmable {
    void swim();
}

// 狗:只实现需要的接口(吃、睡、游)
public class Dog implements Eatable, Sleepable, Swimmable {
    @Override
    public void eat() { System.out.println("狗吃骨头"); }
    @Override
    public void sleep() { System.out.println("狗睡觉"); }
    @Override
    public void swim() { System.out.println("狗会游泳"); }
}

// 鸟:只实现需要的接口(吃、睡、飞)
public class Bird implements Eatable, Sleepable, Flyable {
    @Override
    public void eat() { System.out.println("鸟吃虫子"); }
    @Override
    public void sleep() { System.out.println("鸟睡觉"); }
    @Override
    public void fly() { System.out.println("鸟会飞"); }
}

优势:无任何空实现,接口粒度最小,耦合极低,扩展灵活。

4.3 原则核心要点

  1. 接口最小化:一个接口只包含一组强相关的方法;
  2. 按需实现:客户端只依赖自己需要的接口;
  3. 拒绝胖接口:禁止一个接口包含所有功能。

4.4 架构层面落地

  1. MyBatis Mapper 接口:每个表对应一个细粒度 Mapper,不耦合多表操作;
  2. RESTful API:接口细粒度拆分,/user、/order、/pay 独立接口;
  3. 微服务接口:每个服务提供最小粒度的 RPC 接口,不冗余。

第五章 依赖倒置原则(Dependency Inversion Principle, DIP)

5.1 通俗定义(由浅入深)

高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节,细节依赖抽象。

用大白话讲:面向抽象编程,不要面向具体实现编程

核心概念

  • 高层模块:调用方(业务逻辑层);
  • 低层模块:被调用方(数据访问层、工具层);
  • 抽象:接口 / 抽象类;
  • 细节:接口的实现类。

核心本质

解耦高层与低层模块,让系统不依赖具体实现,只依赖稳定的抽象,实现类可以随意替换,不影响高层逻辑

违背原则的致命痛点

  1. 高层模块硬编码依赖低层具体实现,替换实现需要修改高层代码;
  2. 耦合度极高,系统灵活性极差;
  3. 无法单元测试,难以模拟依赖。

5.2 Java 代码实战:反例 vs 正例

我们以用户业务层依赖数据访问层为例(Java 分层架构经典场景)。

❌ 反例:违背依赖倒置(高层依赖具体实现)

// 低层模块:MySQL具体实现类
public class UserMySqlDao {
    public void getUser() {
        System.out.println("从MySQL查询用户");
    }
}

// 高层模块:业务服务类(直接依赖MySQL具体实现)
public class UserService {
    // 硬编码依赖具体实现,耦合极高!
    private UserMySqlDao userMySqlDao = new UserMySqlDao();

    public void queryUser() {
        userMySqlDao.getUser();
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        UserService userService = new UserService();
        userService.queryUser();
    }
}

问题:如果要把 MySQL 换成 Oracle,必须修改UserService的代码,违背开闭原则,耦合极高。

✅ 正例:遵循依赖倒置(依赖抽象)

核心思路:高层和低层都依赖抽象接口,细节实现依赖抽象

// 1. 抽象DAO接口(高层和低层都依赖这个抽象)
public interface UserDao {
    void getUser();
}

// 2. 低层细节1:MySQL实现类(依赖抽象)
public class UserMySqlDao implements UserDao {
    @Override
    public void getUser() {
        System.out.println("从MySQL查询用户");
    }
}

// 3. 低层细节2:Oracle实现类(扩展,不修改高层)
public class UserOracleDao implements UserDao {
    @Override
    public void getUser() {
        System.out.println("从Oracle查询用户");
    }
}

// 4. 高层模块:依赖抽象接口,不依赖具体实现
public class UserService {
    // 面向抽象编程
    private UserDao userDao;

    // 构造器注入依赖(Spring推荐方式)
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void queryUser() {
        userDao.getUser();
    }
}

// 测试类:随意切换底层实现,高层代码无任何修改
public class Test {
    public static void main(String[] args) {
        // MySQL实现
        UserService service1 = new UserService(new UserMySqlDao());
        service1.queryUser();

        // Oracle实现(无缝切换)
        UserService service2 = new UserService(new UserOracleDao());
        service2.queryUser();
    }
}

优势:底层数据库随意切换,高层业务代码完全不用修改,完美解耦。

5.3 依赖注入的 3 种方式(Java 架构必知)

依赖倒置的落地方式是依赖注入(DI),Spring 框架核心:

  1. 构造器注入(推荐):强制依赖,线程安全,不可变;
  2. Setter 注入:可选依赖,灵活;
  3. 接口注入:极少使用,耦合较高。

5.4 架构层面落地

  1. Spring IOC 容器:完全基于依赖倒置,所有组件依赖抽象,容器注入实现;
  2. 分层架构:Controller→Service→DAO,每层都依赖上一层的接口;
  3. 微服务:服务间依赖 Feign 接口,不依赖具体服务实例。

第六章 迪米特法则(Law of Demeter, LOD)/ 最少知道原则

6.1 通俗定义(由浅入深)

一个对象应该对其他对象保持最少的了解,只和直接的朋友通信,不和陌生人说话。

用大白话讲:不要和陌生人说话。你去买奶茶,只和店员沟通,不用认识奶茶师、原料供应商,减少不必要的依赖。

核心本质

降低类之间的耦合度,隐藏对象内部实现细节,只暴露必要的方法。

直接朋友的定义(必背)

一个对象的直接朋友只有 5 类:

  1. 当前对象本身(this);
  2. 对象的成员变量;
  3. 方法的入参;
  4. 方法的返回值;
  5. 当前对象创建的对象。

非直接朋友 = 陌生人,禁止直接调用陌生人的方法。

违背原则的致命痛点

  1. 类之间依赖关系复杂,形成「网状耦合」;
  2. 修改一个类,影响大量无关类;
  3. 内部细节暴露,安全性差。

6.2 Java 代码实战:反例 vs 正例

场景:租户租房,租户、房东、房屋三个对象。

❌ 反例:违背迪米特法则(租户直接访问房屋细节)

// 房屋类(细节:水电表、门窗)
public class House {
    public void checkWater() { System.out.println("检查水表"); }
    public void checkDoor() { System.out.println("检查门窗"); }
}

// 租户类(直接调用房屋的细节方法,和陌生人通信)
public class Tenant {
    public void rentHouse(House house) {
        // 租户直接访问房屋内部细节,违背迪米特法则!
        house.checkWater();
        house.checkDoor();
        System.out.println("租户入住");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Tenant tenant = new Tenant();
        House house = new House();
        tenant.rentHouse(house);
    }
}

问题:租户直接依赖房屋的细节,耦合极高,房屋修改细节,租户必须改代码。

✅ 正例:遵循迪米特法则(只和直接朋友房东通信)

核心思路:租户只和房东交互,房东管理房屋细节,租户不接触房屋

// 1. 房屋类(内部细节隐藏,私有方法)
public class House {
    private void checkWater() { System.out.println("检查水表"); }
    private void checkDoor() { System.out.println("检查门窗"); }

    // 对外暴露一个方法,隐藏内部细节
    public void checkHouse() {
        checkWater();
        checkDoor();
    }
}

// 2. 房东类(租户的直接朋友,管理房屋)
public class Landlord {
    private House house = new House();

    public void provideHouse() {
        house.checkHouse();
        System.out.println("房东提供房屋");
    }
}

// 3. 租户类(只和直接朋友房东通信,不接触房屋)
public class Tenant {
    public void rentHouse(Landlord landlord) {
        landlord.provideHouse();
        System.out.println("租户入住");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Tenant tenant = new Tenant();
        Landlord landlord = new Landlord();
        tenant.rentHouse(landlord);
    }
}

优势:租户只依赖房东,房屋细节完全隐藏,耦合极低。

6.3 原则核心要点

  1. 隐藏内部细节:尽量用 private 方法,少用 public 方法;
  2. 减少依赖:只和直接朋友通信,拒绝间接依赖;
  3. 封装隔离:通过中间类隔离陌生人依赖。

6.4 架构层面落地

  1. DDD 领域驱动设计:聚合根对外暴露接口,内部实体禁止外部直接访问;
  2. 微服务网关:客户端只访问网关,不直接调用微服务实例;
  3. 门面模式(Facade):通过门面类封装复杂子系统,客户端只访问门面。

第七章 合成复用原则(Composite Reuse Principle, CRP)

7.1 通俗定义(由浅入深)

尽量使用对象组合 / 聚合,而不是类继承来实现代码复用。

用大白话讲:优先组合,慎用继承

核心概念

  • 继承:白盒复用,暴露父类所有细节,耦合极高,Java 单继承限制;
  • 组合:黑盒复用,只调用对象方法,不暴露细节,低耦合,无数量限制。

聚合 vs 组合

  1. 聚合:整体与部分可分离(汽车和轮胎,轮胎坏了可以换);
  2. 组合:整体与部分不可分离(人体和器官,器官坏了人就废了)。

违背原则的致命痛点

  1. 继承破坏封装,父类修改子类必改;
  2. Java 单继承限制,无法复用多个类的功能;
  3. 复用灵活性极差,无法动态替换。

7.2 Java 代码实战:反例 vs 正例

场景:汽车复用引擎、轮胎功能

❌ 反例:违背合成复用(继承实现复用)

// 引擎类
public class Engine {
    public void start() { System.out.println("引擎启动"); }
}

// 轮胎类
public class Wheel {
    public void run() { System.out.println("轮胎转动"); }
}

// 汽车类:继承Engine(Java单继承,无法再继承Wheel)
public class Car extends Engine {
    // 无法复用Wheel的功能,继承有致命缺陷!
    public void drive() {
        start();
        System.out.println("汽车行驶");
    }
}

问题:Java 单继承,汽车只能继承一个类,无法同时复用引擎和轮胎的功能。

✅ 正例:遵循合成复用(组合实现复用)

核心思路:汽车组合引擎和轮胎对象,灵活复用多个功能

// 1. 引擎类
public class Engine {
    public void start() { System.out.println("引擎启动"); }
}

// 2. 轮胎类
public class Wheel {
    public void run() { System.out.println("轮胎转动"); }
}

// 3. 汽车类:组合Engine和Wheel(聚合复用,无继承限制)
public class Car {
    // 组合多个对象,灵活复用
    private Engine engine = new Engine();
    private Wheel wheel = new Wheel();

    public void drive() {
        engine.start();
        wheel.run();
        System.out.println("汽车行驶");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        car.drive();
    }
}

优势:可以组合任意多个对象,无继承限制,低耦合,可动态替换组件。

7.3 原则核心要点

  1. 优先组合:90% 的复用场景用组合,继承仅用在稳定的父子关系;
  2. 黑盒复用:组合不暴露被复用对象的细节,安全性高;
  3. 动态复用:运行时可替换组合的对象,继承是静态的。

7.4 架构层面落地

  1. SpringBoot Starter:组合各种组件(Redis、MQ、MyBatis),无需继承;
  2. 组件化开发:前端 / 后端组件通过组合拼装成系统;
  3. 装饰器模式:通过组合动态扩展功能,替代继承。

第八章 七大原则关系与架构师落地实践

8.1 七大原则的内在联系(核心总结)

  1. 基础层:单一职责原则(粒度规范);
  2. 核心层:开闭原则(设计目标);
  3. 规范层:里氏替换(继承)、接口隔离(接口)、依赖倒置(依赖);
  4. 优化层:迪米特法则(耦合)、合成复用(复用)。

所有原则最终都指向一个目标:高内聚、低耦合,拥抱变化。

8.2 架构师落地三大准则

  1. 拒绝过度设计:原则是指导,不是教条。小项目不用强行拆分,大项目严格遵循;
  2. 业务优先:所有设计都要服务于业务,不做无意义的抽象;
  3. 迭代优化:代码不是一次写好的,通过原则逐步重构优化。

8.3 企业级实战案例:电商订单系统

  1. 单一职责:订单服务、支付服务、物流服务拆分;
  2. 开闭原则:新增促销规则,只加实现类,不改订单核心;
  3. 里氏替换:订单抽象类,普通订单、秒杀订单透明替换;
  4. 接口隔离:订单接口细粒度拆分,/create、/cancel、/pay;
  5. 依赖倒置:Service 依赖 DAO 接口,不依赖具体实现;
  6. 迪米特法则:用户只访问订单服务,不访问库存、物流细节;
  7. 合成复用:订单组合支付、库存、物流组件,不继承。

8.4 常见误区(架构师避坑)

  1. 误区 1:单一职责 = 拆得越细越好(错,适度拆分);
  2. 误区 2:开闭原则 = 完全不修改代码(错,核心代码不修改,边缘代码可优化);
  3. 误区 3:所有场景都用组合(错,稳定的父子关系用继承,如 HashMap 继承 AbstractMap)。

最终总结(万字核心提炼)

软件设计七大原则是 Java 架构师的立身之本,从代码粒度到架构设计,层层递进:

  1. 单一职责:一个组件只做一件事;
  2. 开闭原则:扩展开放,修改关闭;
  3. 里氏替换:子类透明替换父类;
  4. 接口隔离:接口小而专,拒绝胖接口;
  5. 依赖倒置:面向抽象编程;
  6. 迪米特法则:只和直接朋友通信;
  7. 合成复用:优先组合,慎用继承。

遵循这七大原则,你写的代码将具备高内聚、低耦合、易扩展、易维护、易测试五大核心优势,无论是单体应用还是微服务架构,都能轻松应对需求迭代,成为真正的工业级 Java 架构师。

posted @ 2026-03-30 23:35  bright_ye  阅读(24)  评论(0)    收藏  举报