软件设计七大原则
软件设计七大原则
作为 Java 架构师,我深耕企业级应用设计、微服务架构、分布式系统研发十余年,见过太多项目从「简洁优雅」走向「臃肿腐化」:需求迭代时改一处代码牵一发而动全身,测试成本指数级上升,新人接手代码寸步难行,最终不得不重构甚至推倒重来。根源只有一个:违背了软件设计的七大核心原则。
软件设计七大原则是面向对象设计(OOD)的基石,是全球架构师总结的「代码设计黄金准则」,也是SOLID 原则 + 迪米特法则 + 合成复用原则的统称:
- 单一职责原则(SRP)
- 开闭原则(OCP)
- 里氏替换原则(LSP)
- 接口隔离原则(ISP)
- 依赖倒置原则(DIP)
- 迪米特法则(LOD / 最少知道原则)
- 合成复用原则(CRP)
这七大原则由浅入深、层层递进:单一职责是基础粒度规范,开闭原则是核心设计目标,里氏替换规范继承,接口隔离规范接口,依赖倒置规范依赖关系,迪米特法则控制耦合,合成复用规范代码复用。
本文全程用通俗语言 + Java 实战代码 + 架构落地场景拆解,拒绝晦涩学术定义,让新手能看懂、老手能落地,总字数超 12000 字,彻底吃透七大原则。
前置铺垫:为什么要学七大原则?
先问一个架构师核心问题:什么是好的软件设计?
答案只有 8 个字:高内聚、低耦合。
- 高内聚:一个模块 / 类只做一件事,内部逻辑紧密关联;
- 低耦合:模块之间依赖最少,修改一个模块不影响其他模块。
而软件设计七大原则,就是实现「高内聚、低耦合」的唯一方法论。
新手写代码:以「实现功能」为目标,能跑就行;
架构师写代码:以「应对变化」为目标,拥抱需求迭代,拒绝代码腐化。
在企业级 Java 项目中(电商、支付、微服务、分布式系统),需求永远在变:新增促销规则、修改支付方式、扩展用户权限、替换中间件... 七大原则就是让你的代码不用改原有逻辑,仅通过扩展就能适配新需求。
第一章 单一职责原则(Single Responsibility Principle, SRP)
1.1 通俗定义(由浅入深)
一个类、接口、方法,只负责一项独立的职责,只有一个引起它变化的原因。
用大白话讲:不要让一个类干多份活。就像现实中,医生只看病、老师只教书、厨师只做饭,让一个人同时干三份工作,必然样样做不好。
核心本质
高内聚:将不同的业务逻辑拆分,让每个组件只关注自己的核心职责,从根源上避免代码耦合。
违背原则的致命痛点
- 代码臃肿:一个类几千行,阅读、维护难度爆炸;
- 复用性差:无法单独复用其中一个功能,必须引入整个冗余类;
- 测试困难:一个方法修改,所有关联功能都要重新测试;
- 维护灾难:修改一处逻辑,会意外破坏其他无关功能。
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.4 架构层面落地
单一职责是微服务拆分的核心依据:
- 电商系统:用户服务、订单服务、支付服务、商品服务(每个服务只做一件事);
- 后端分层:Controller(接收请求)、Service(业务逻辑)、DAO(数据访问)(分层各司其职)。
第二章 开闭原则(Open Closed Principle, OCP)
2.1 通俗定义(由浅入深)
软件实体(类、模块、方法)对扩展开放,对修改关闭。
用大白话讲:新增需求时,只加新代码,不改老代码。老代码是经过测试验证的稳定代码,修改必然引入 bug 风险。
核心本质
开闭原则是七大原则的核心,其他 6 大原则都是为了实现开闭原则。它的目标是:保护原有代码稳定性,提升系统扩展性。
违背原则的致命痛点
- 修改老代码,破坏原有功能,回归测试成本极高;
- 代码腐化,长期迭代后变成「屎山代码」;
- 无法灵活适配新需求,扩展性为 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 原则核心要点
- 抽象是关键:用接口 / 抽象类定义通用规范,细节由实现类扩展;
- 核心逻辑稳定:底层核心代码禁止修改,上层业务通过扩展实现;
- 拥抱变化:所有易变的业务逻辑,都抽离为可扩展的实现。
2.4 架构层面落地
- Spring 框架:AOP 面向切面编程,不修改业务代码,扩展日志、事务、权限;
- 电商促销:新增优惠券、满减、秒杀,只加促销规则实现类,不改订单核心代码;
- 插件化开发:IDE 插件、浏览器插件,扩展功能不修改主程序。
第三章 里氏替换原则(Liskov Substitution Principle, LSP)
3.1 通俗定义(由浅入深)
所有引用父类的地方,必须能透明地使用其子类对象,程序逻辑不变。
用大白话讲:子类可以扩展父类功能,但不能破坏父类原有的核心功能。儿子可以比父亲多会技能,但不能把父亲会的本事搞砸。
核心本质
规范继承关系,避免滥用继承,保证多态的正确性。继承是 OOP 三大特性之一,但滥用继承会破坏封装,里氏替换就是继承的「紧箍咒」。
违背原则的致命痛点
- 子类替换父类后,程序逻辑出错;
- 继承破坏封装,父类的核心逻辑被子类篡改;
- 多态失效,系统稳定性崩塌。
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 条核心规则(架构师必背)
- 子类必须实现父类的抽象方法,不能拒绝实现;
- 子类可以扩展自己的方法,但不能重写父类的非抽象方法;
- 子类覆写方法时,入参要比父类更宽松,返回值要比父类更严格;
- 子类不能抛出父类不支持的异常。
3.4 架构层面落地
- 面向抽象编程:方法参数、返回值用父类 / 接口,子类灵活替换;
- Spring IOC:容器注入接口,底层实现类随意切换,不影响上层逻辑;
- 多态设计:系统底层依赖抽象,上层业务灵活扩展。
第四章 接口隔离原则(Interface Segregation Principle, ISP)
4.1 通俗定义(由浅入深)
客户端不应该依赖它不需要的接口;类间的依赖关系,应该建立在最小的接口上。
用大白话讲:接口要小而专,不要大而全。禁止设计「胖接口」(一个接口包含几十个方法),客户端用不到的方法,被迫空实现,违背设计原则。
核心本质
拆分细粒度接口,降低接口耦合,避免客户端被迫实现无用方法。
与单一职责的区别
- 单一职责:针对类 / 方法的业务职责,强调「做一件事」;
- 接口隔离:针对接口的粒度,强调「客户端按需使用」。
违背原则的致命痛点
- 胖接口臃肿,维护困难;
- 实现类被迫空实现无用方法,代码冗余;
- 接口修改,所有实现类都要修改,耦合极高。
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 原则核心要点
- 接口最小化:一个接口只包含一组强相关的方法;
- 按需实现:客户端只依赖自己需要的接口;
- 拒绝胖接口:禁止一个接口包含所有功能。
4.4 架构层面落地
- MyBatis Mapper 接口:每个表对应一个细粒度 Mapper,不耦合多表操作;
- RESTful API:接口细粒度拆分,/user、/order、/pay 独立接口;
- 微服务接口:每个服务提供最小粒度的 RPC 接口,不冗余。
第五章 依赖倒置原则(Dependency Inversion Principle, DIP)
5.1 通俗定义(由浅入深)
高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节,细节依赖抽象。
用大白话讲:面向抽象编程,不要面向具体实现编程。
核心概念
- 高层模块:调用方(业务逻辑层);
- 低层模块:被调用方(数据访问层、工具层);
- 抽象:接口 / 抽象类;
- 细节:接口的实现类。
核心本质
解耦高层与低层模块,让系统不依赖具体实现,只依赖稳定的抽象,实现类可以随意替换,不影响高层逻辑。
违背原则的致命痛点
- 高层模块硬编码依赖低层具体实现,替换实现需要修改高层代码;
- 耦合度极高,系统灵活性极差;
- 无法单元测试,难以模拟依赖。
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 框架核心:
- 构造器注入(推荐):强制依赖,线程安全,不可变;
- Setter 注入:可选依赖,灵活;
- 接口注入:极少使用,耦合较高。
5.4 架构层面落地
- Spring IOC 容器:完全基于依赖倒置,所有组件依赖抽象,容器注入实现;
- 分层架构:Controller→Service→DAO,每层都依赖上一层的接口;
- 微服务:服务间依赖 Feign 接口,不依赖具体服务实例。
第六章 迪米特法则(Law of Demeter, LOD)/ 最少知道原则
6.1 通俗定义(由浅入深)
一个对象应该对其他对象保持最少的了解,只和直接的朋友通信,不和陌生人说话。
用大白话讲:不要和陌生人说话。你去买奶茶,只和店员沟通,不用认识奶茶师、原料供应商,减少不必要的依赖。
核心本质
降低类之间的耦合度,隐藏对象内部实现细节,只暴露必要的方法。
直接朋友的定义(必背)
一个对象的直接朋友只有 5 类:
- 当前对象本身(this);
- 对象的成员变量;
- 方法的入参;
- 方法的返回值;
- 当前对象创建的对象。
非直接朋友 = 陌生人,禁止直接调用陌生人的方法。
违背原则的致命痛点
- 类之间依赖关系复杂,形成「网状耦合」;
- 修改一个类,影响大量无关类;
- 内部细节暴露,安全性差。
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 原则核心要点
- 隐藏内部细节:尽量用 private 方法,少用 public 方法;
- 减少依赖:只和直接朋友通信,拒绝间接依赖;
- 封装隔离:通过中间类隔离陌生人依赖。
6.4 架构层面落地
- DDD 领域驱动设计:聚合根对外暴露接口,内部实体禁止外部直接访问;
- 微服务网关:客户端只访问网关,不直接调用微服务实例;
- 门面模式(Facade):通过门面类封装复杂子系统,客户端只访问门面。
第七章 合成复用原则(Composite Reuse Principle, CRP)
7.1 通俗定义(由浅入深)
尽量使用对象组合 / 聚合,而不是类继承来实现代码复用。
用大白话讲:优先组合,慎用继承。
核心概念
- 继承:白盒复用,暴露父类所有细节,耦合极高,Java 单继承限制;
- 组合:黑盒复用,只调用对象方法,不暴露细节,低耦合,无数量限制。
聚合 vs 组合
- 聚合:整体与部分可分离(汽车和轮胎,轮胎坏了可以换);
- 组合:整体与部分不可分离(人体和器官,器官坏了人就废了)。
违背原则的致命痛点
- 继承破坏封装,父类修改子类必改;
- Java 单继承限制,无法复用多个类的功能;
- 复用灵活性极差,无法动态替换。
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 原则核心要点
- 优先组合:90% 的复用场景用组合,继承仅用在稳定的父子关系;
- 黑盒复用:组合不暴露被复用对象的细节,安全性高;
- 动态复用:运行时可替换组合的对象,继承是静态的。
7.4 架构层面落地
- SpringBoot Starter:组合各种组件(Redis、MQ、MyBatis),无需继承;
- 组件化开发:前端 / 后端组件通过组合拼装成系统;
- 装饰器模式:通过组合动态扩展功能,替代继承。
第八章 七大原则关系与架构师落地实践
8.1 七大原则的内在联系(核心总结)
- 基础层:单一职责原则(粒度规范);
- 核心层:开闭原则(设计目标);
- 规范层:里氏替换(继承)、接口隔离(接口)、依赖倒置(依赖);
- 优化层:迪米特法则(耦合)、合成复用(复用)。
所有原则最终都指向一个目标:高内聚、低耦合,拥抱变化。
8.2 架构师落地三大准则
- 拒绝过度设计:原则是指导,不是教条。小项目不用强行拆分,大项目严格遵循;
- 业务优先:所有设计都要服务于业务,不做无意义的抽象;
- 迭代优化:代码不是一次写好的,通过原则逐步重构优化。
8.3 企业级实战案例:电商订单系统
- 单一职责:订单服务、支付服务、物流服务拆分;
- 开闭原则:新增促销规则,只加实现类,不改订单核心;
- 里氏替换:订单抽象类,普通订单、秒杀订单透明替换;
- 接口隔离:订单接口细粒度拆分,/create、/cancel、/pay;
- 依赖倒置:Service 依赖 DAO 接口,不依赖具体实现;
- 迪米特法则:用户只访问订单服务,不访问库存、物流细节;
- 合成复用:订单组合支付、库存、物流组件,不继承。
8.4 常见误区(架构师避坑)
- 误区 1:单一职责 = 拆得越细越好(错,适度拆分);
- 误区 2:开闭原则 = 完全不修改代码(错,核心代码不修改,边缘代码可优化);
- 误区 3:所有场景都用组合(错,稳定的父子关系用继承,如 HashMap 继承 AbstractMap)。
最终总结(万字核心提炼)
软件设计七大原则是 Java 架构师的立身之本,从代码粒度到架构设计,层层递进:
- 单一职责:一个组件只做一件事;
- 开闭原则:扩展开放,修改关闭;
- 里氏替换:子类透明替换父类;
- 接口隔离:接口小而专,拒绝胖接口;
- 依赖倒置:面向抽象编程;
- 迪米特法则:只和直接朋友通信;
- 合成复用:优先组合,慎用继承。
遵循这七大原则,你写的代码将具备高内聚、低耦合、易扩展、易维护、易测试五大核心优势,无论是单体应用还是微服务架构,都能轻松应对需求迭代,成为真正的工业级 Java 架构师。
浙公网安备 33010602011771号