SOLID原则详解:提升软件设计质量的关键

前言

关于设计原则SOLID具体指的是什么,怎么理解这些设计原则,我觉得有必要记录一笔,毕竟这个设计原则确实经常在关键技术文档中提及,在编程思想中提及,在日常的开发中使用,但是对我来说,似乎知道但又不那么明确,我希望自己对设计原则的思想有一个更加准确和全面的理解,也想明确如果没有这个设计原则会如何?此设计原则的亮点和优势是什么?我在日常开发中怎么使用到这些设计原则的?
本文就是基于以上问题的总结归纳,方便自己日后复盘。
说明:汇总风格和内容借助AI工具

一、什么是SOLID?

SOLID是面向对象编程和软件设计的五个基本原则的首字母缩写,这些原则帮助我们编写更易于维护、扩展和理解的代码。

  1. S - 单一职责原则 (Single Responsibility Principle)
  2. O - 开闭原则 (Open/Closed Principle)
  3. L - 里氏替换原则 (Liskov Substitution Principle)
  4. I - 接口隔离原则 (Interface Segregation Principle)
  5. D - 依赖倒置原则 (Dependency Inversion Principle)

1. 单一职责原则(SRP)

  • 核心:一个类应该只有一个引起它变化的原因(即只有一个职责)。
  • 关键点
    • 方法层面:一个方法只做一件事(如saveStudent()不应同时包含验证和存储逻辑)。
    • 类层面:Student类管理学生属性,若需日志记录,应拆分出StudentLogger类。
  • 优势:降低复杂度、提高可维护性,修改一个功能时不会意外影响其他功能。
  • 现实类比:就像餐厅里厨师负责烹饪,服务员负责上菜,收银员负责结账,各司其职,而不是一个人做所有事情。

日常开发中的问题:忽视SRP会导致"上帝类"(God Class),修改一处可能影响多处功能,测试困难,代码难以复用。

  • 反例:
    class Student {void saveToDatabase() { /* 数据库操作 */ }void generateReport() { /* 生成PDF */ } // 违反SRP
    }
    

2. 开闭原则(OCP)

  • 核心:通过扩展(继承/组合)添加新功能,而非修改已有代码。
  • 关键点
    • 多态是手段之一,但OCP更强调抽象(接口/抽象类)的设计。
    • 示例:支付系统支持新支付方式时,应实现Payment接口,而非修改原有代码。
    interface Payment { void pay(); }
    class CreditCard implements Payment { /* 无需修改现有类 */ }
    
  • 优势:减少回归测试风险,提高系统可扩展性。
  • 现实类比:USB接口设计 - 你可以插入各种设备(扩展开放),而不需要修改电脑的USB接口本身(修改关闭)。

日常开发中的问题:忽视OCP会导致每次需求变更都要修改核心类,增加回归测试负担,引入新bug的风险高。

  • 反例:
class Shape {private String type;public double calculateArea() {if (type.equals("circle")) {// 计算圆形面积} else if (type.equals("rectangle")) {// 计算矩形面积}// 每添加一个新形状都要修改这个方法}
}

3. 里氏替换原则(LSP)

  • 核心:子类必须能够替换父类而不破坏程序逻辑(行为一致性)。
  • 关键点:
    • 子类可扩展父类功能,但不能改变父类的契约(如输入/输出约束)。
  • 优势:保证继承体系的健壮性,避免运行时意外错误。
  • 现实类比:正方形是长方形的特例,但如果长方形有设置不同长宽的方法,正方形继承长方形就会有问题,因为正方形长宽必须相同。

日常开发中的问题:忽视LSP会导致在使用多态时出现意外行为,子类无法真正替代父类,增加了代码的脆弱性。

  • 反例:
    父类Birdfly()方法,子类Penguin重写为空方法——违反LSP。
class Bird {public void fly() {System.out.println("Flying");}
}class Ostrich extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("鸵鸟不会飞!");}
}public class Main {public static void makeBirdFly(Bird bird) {bird.fly();  // 对于鸵鸟,这会抛出异常}
}

4. 接口隔离原则(ISP)

  • 核心:客户端不应被迫依赖它不需要的接口方法。
  • 关键点
    • 将庞大接口拆分为更小、更具体的接口(如PrinterScanner分开,而非合并为MultiFunctionDevice)。
    • 示例:
      interface Printable { void print(); }
      interface Scannable { void scan(); }
      class SimplePrinter implements Printable { ... } // 无需实现scan()
      
  • 优势:减少接口污染,降低依赖耦合。
  • 现实类比:多功能工具 vs 专用工具 ,你不会用瑞士军刀上的剪刀功能来剪头发(虽然可以,但不合适)。

日常开发中的问题:忽视ISP会导致"胖接口",实现类被迫提供空实现或抛出异常,接口变得难以理解和维护。

  • 反例:
interface Worker {void work();void eat();void sleep();
}class HumanWorker implements Worker {// 实现所有方法
}class RobotWorker implements Worker {public void work() {// 机器人可以工作}public void eat() {throw new UnsupportedOperationException("机器人不需要吃饭");}public void sleep() {throw new UnsupportedOperationException("机器人不需要睡觉");}
}

5. 依赖倒置原则(DIP)

  • 核心
    高层模块不应直接依赖低层模块,二者都应依赖抽象(接口或抽象类)。
    抽象不应依赖细节(具体实现),细节应依赖抽象。
  • 关键点
    “反转”传统的依赖关系方向,使得软件的设计更加灵活、可复用,并且更容易应对变化。
  • 现实类比:电源插座提供标准接口(抽象),各种电器(具体实现)只要符合接口标准就能使用,插座不需要知道具体是什么电器。

日常开发中的问题:忽视DIP会导致高层模块与低层模块紧耦合,难以替换实现,单元测试困难(因为难以mock依赖)。

  • 反例:
class LightBulb {public void turnOn() {// 开灯}public void turnOff() {// 关灯}
}class Switch {private LightBulb bulb;public Switch(LightBulb bulb) {this.bulb = bulb;}public void operate() {// 直接依赖具体实现bulb.turnOn();}
}

二、SpringBoot+MyBatis后台系统中的SOLID原则实践

1. 单一职责原则(SRP)在SpringBoot中的体现

反面案例(违反SRP)

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 用户CRUDpublic User getUserById(Long id) { /*...*/ }public void saveUser(User user) { /*...*/ }// 密码加密public String encryptPassword(String raw) { /*...*/ }// 权限检查public boolean checkPermission(User user) { /*...*/ }// 日志记录public void writeLog(User user, String action) { /*...*/ }
}

问题:这个Service类做了太多事情,违反了SRP。如果密码加密算法或日志记录方式需要修改,都要改这个类。

正面案例(遵循SRP)

// 用户CRUD服务
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate PermissionChecker permissionChecker;@Autowiredprivate UserActionLogger actionLogger;public User getUserById(Long id) { /*...*/ }public void saveUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword()));userMapper.insert(user);actionLogger.log(user, "CREATE");}
}// 密码加密组件
@Component
public class BCryptPasswordEncoder implements PasswordEncoder {public String encode(String raw) { /* 使用BCrypt加密 */ }
}// 权限检查组件
@Component
public class PermissionChecker {public boolean check(User user) { /*...*/ }
}// 日志记录组件
@Component
public class UserActionLogger {public void log(User user, String action) { /*...*/ }
}

SpringBoot中的体现

  • Controller只处理HTTP请求和响应
  • Service只处理业务逻辑
  • Mapper只负责数据库操作
  • 各种Util/Helper类各司其职

2. 开闭原则(OCP)在MyBatis中的体现

场景:我们需要支持多种数据库查询方式(ID查询、姓名查询、条件组合查询)

反面案例(违反OCP)

@Mapper
public interface UserMapper {@Select("SELECT * FROM user WHERE ${condition}") List<User> findByCondition(String condition); // 危险!SQL注入风险// 每新增一种查询方式都要添加新方法
}

正面案例(遵循OCP)

使用MyBatis-Plus,它的Wrapper设计就符合OCP:

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 使用条件构造器,不需要修改原有代码就能扩展新查询方式public List<User> findUsers(String name, Integer age) {QueryWrapper<User> wrapper = new QueryWrapper<>();if (name != null) {wrapper.like("name", name);}if (age != null) {wrapper.eq("age", age);}return userMapper.selectList(wrapper);}
}

MP的设计

  • 通过Wrapper可以灵活组合查询条件
  • 新增查询条件不需要修改Mapper接口
  • 符合"对扩展开放,对修改关闭"

3. 里氏替换原则(LSP)在权限系统中的应用

场景:我们有普通用户和管理员用户

反面案例(违反LSP)

class User {public void deletePost(Post post) {// 基础权限检查}
}class Admin extends User {@Overridepublic void deletePost(Post post) {throw new UnsupportedOperationException("管理员应该用adminDeletePost方法");}public void adminDeletePost(Post post) {// 跳过权限检查}
}

问题:Admin无法替换User,因为重写的方法抛出了异常。

正面案例(遵循LSP)

interface PostDeleter {void deletePost(Post post);
}class UserPostDeleter implements PostDeleter {public void deletePost(Post post) {// 基础权限检查}
}class AdminPostDeleter implements PostDeleter {public void deletePost(Post post) {// 管理员有特殊处理,但不抛出异常}
}// 使用时
@Autowired
private Map<String, PostDeleter> deleterMap; // Spring会自动注入所有实现public void deletePost(Post post, String userType) {PostDeleter deleter = deleterMap.get(userType + "PostDeleter");deleter.deletePost(post); // 无论什么用户类型都能安全调用
}

4. 接口隔离原则(ISP)在Service层设计中的应用

场景:用户操作有读操作和写操作,有些客户端只需要读功能

反面案例(违反ISP)

public interface UserService {User getById(Long id);List<User> findAll();void save(User user);void delete(Long id);void resetPassword(Long id);// 很多方法...
}// 报表系统只需要读功能,但被迫实现所有方法

正面案例(遵循ISP)

// 拆分接口
public interface UserReadService {User getById(Long id);List<User> findAll();
}public interface UserWriteService {void save(User user);void delete(Long id);void resetPassword(Long id);
}@Service
public class UserServiceImpl implements UserReadService, UserWriteService {// 实现所有方法
}// 报表系统只需要注入UserReadService
@Autowired
private UserReadService userReadService;

5. 依赖倒置原则(DIP)在SpringBoot中的体现

场景:用户数据存储可能使用MySQL或Redis

反面案例(违反DIP)

@Service
public class UserService {// 直接依赖具体实现private UserMySQLRepository userRepository = new UserMySQLRepository();// 如果改用Redis需要修改代码
}

正面案例(遵循DIP)

// 定义抽象接口
public interface UserRepository {User findById(Long id);void save(User user);
}// MySQL实现
@Repository
public class UserMySQLRepository implements UserRepository {// 实现方法
}// Redis实现
@Repository
public class UserRedisRepository implements UserRepository {// 实现方法
}@Service
public class UserService {@Autowiredprivate UserRepository userRepository; // 依赖抽象// 可以通过@Qualifier或Profile决定注入哪个实现
}

SpringBoot天生支持DIP

  • 通过@Autowired注入接口
  • 具体实现由Spring容器管理
  • 轻松替换实现而不修改业务代码

三、实际应用建议

(1)实际应用

  • Spring框架:依赖注入(DI)是DIP的典型实现。
  • Java集合框架List接口(抽象)与ArrayList/LinkedList(实现)遵循DIP和OCP。
  • 日志库:SLF4J是抽象,Logback/Log4j是具体实现,符合DIP。

(2)实际编程中的选择

  • 写业务代码时:优先用 SRPDIP(拆分职责+依赖接口)。
  • 设计架构时:重点考虑 OCPISP(方便扩展+接口精简)。
  • review代码时:检查 LSP(子类是否破坏父类行为)。

后记

SOLID不是教条,而是帮助写出更健壮代码的工具。在SpringBoot项目中,很多设计已经遵循了这些原则,我们只需要有意识地应用它们。

参考链接

SOLID,面向对象设计五大基本原则

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/78957.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用 ONLYOFFICE 恢复之前的文件版本?

如何使用 ONLYOFFICE 恢复之前的文件版本&#xff1f; https://www.onlyoffice.com/blog/zh-hans/2023/04/how-to-use-version-history

简简单单实现一个Python+Selenium的自动化测试框架

什么是Selenium&#xff1f; Selenium是一个基于浏览器的自动化测试工具&#xff0c;它提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分&#xff1a;Selenium IDE、Selenium WebDriver 和Selenium Grid。 Selenium IDE&#xff1a;Firefox的…

Java设计模式之中介者模式:从入门到架构级实践

一、什么是中介者模式&#xff1f; 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;其核心思想是通过引入一个中介对象来封装多个对象之间的交互关系。这种模式将原本复杂的网状通信结构转换为星型结构&#xff0c;类似于现实生活中的机…

Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化

一、软件介绍 文末提供源码和程序下载学习 Trinity三位一体开源程序是可解释的 AI 分析工具和 3D 可视化。Trinity 提供性能分析和 XAI 工具&#xff0c;非常适合深度学习系统或其他执行复杂分类或解码的模型。 二、软件作用和特征 Trinity 通过结合具有超维感知能力的不同交…

LeetCode 热题 100_单词拆分(86_139_中等_C++)(动态规划)

LeetCode 热题 100_单词拆分&#xff08;86_139&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;动态规划&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;动态规划&#xff09;&a…

VM虚拟机安装及Ubuntu安装配置

VM虚拟机安装及Ubuntu安装配置 1、VM虚拟机安装2、创建虚拟机3、Ubuntu系统安装4、编译环境配置4.1 、Ubuntu和 Windows文件互传 文件互传4.1.1、 开启Ubunt下的FTP服务 4.2、 Ubuntu下NFS和SSH服务开启4.2.1、 NFS服务开启4.2.2、 SSH服务开启 4.3、 交叉编译器安装4.3.1 安装…

【KWDB 创作者计划】_产品技术解读_1

【KWDB 创作者计划】_产品技术解读_1 一、存储引擎:高性能混合存储架构1. 存储模型设计2. 存储压缩与编码3. 持久化策略二、KWDB 组件源码解析1. 核心模块分层架构2. 关键组件源码剖析三、KWDB 特性代码通读1. 实时分析能力(Real-Time OLAP)2. 混合负载隔离(HTAP)3. 智能索…

高速电路中的电阻、电容的选型及应用

2.1 电阻的应用 2.1.1 与电阻相关的经典案例 如果说芯片是电路的骨架&#xff0c;那么电阻就是在芯片之间起连接作用的关节。电阻的阻值、布放位置等&#xff0c;对设计的成功起着至关重要的作用。 【案例2.1】串联电阻过大&#xff0c;导致板间告警失败 某产品由业务板和主…

springBoot接入文心一言

文章目录 效果接入步骤项目接入配置类&#xff1a;WenXinYiYan前端vue代码js代码 后端mapper层service层controller层 测试代码 效果 先来看一下最后实现的效果 &#xff08;1&#xff09;未点击前的功能页面 &#xff08;2&#xff09;点击后的页面 &#xff08;3&#xff…

css解决边框四个角有颜色

效果 html <div class"gradient-corner">2021年</div>css background:/* 左上角横线 */linear-gradient(90deg, rgb(5, 150, 247) 9px, transparent 0) 0 0,/* 左上角竖线 */linear-gradient(0deg, rgb(5, 150, 247) 9px, transparent 0) 0 0,/* 右上…

自动化三维扫描:CASAIM外观尺寸智能检测

制造业向智能化、数字化加速转型&#xff0c;传统检测方式因效率低、精度差、数据断层等问题&#xff0c;已难以满足现代工业对精密测量与实时质控的需求。CASAIM依托前沿技术实力&#xff0c;以自动化三维扫描为核心&#xff0c;为工业检测提供了从数据采集到智能分析的全流程…

突破亚马逊壁垒,Web Unlocker API 助您轻松获取数据

目录 一、Web Unlocker API简介二、开始使用Web Unlocker API1、首先进入控制台页面&#xff0c;点击左侧第一个tab键“代理 & 抓取基础设施”&#xff0c;找到“网页解锁器”&#xff0c;开始使用。2、进入网页解锁器页面后&#xff0c;填写通道名称&#xff0c;添加简短描…

【力扣05】最长回文子串

0. 引言 ●子串(substring&#xff09;&#xff1a;原始字符串的一个连续子集; ●子序列&#xff08;subsequence&#xff09;&#xff1a;原始字符串的一个子集。 1. 什么叫回文串&#xff1f; 如果一个字符串正着读和反着读是一样的&#xff0c;那它就是回文串。[1] 例如&…

统计销量前十的订单

传入参数&#xff1a; 传入begin和end两个时间 返回参数 返回nameList和numberList两个String类型的列表 controller层 GetMapping("/top10")public Result<SalesTop10ReportVO> top10(DateTimeFormat(pattern "yyyy-MM-dd") LocalDate begin,Dat…

【HDFS入门】HDFS核心组件Secondary NameNode角色职责与运行机制解析

目录 1 Secondary NameNode的角色定位与常见误解 2 核心职责详解 2.1 核心功能职责 2.2 与NameNode的协作关系 3 运行机制深度剖析 3.1 检查点触发机制 3.2 元数据合并流程 4 与Hadoop 2.0 HA架构的对比 5 配置调优指南 5.1 关键配置参数 5.2 性能优化建议 6 实践应…

MySQL存储引擎:存储什么意思?引擎什么意思?存储引擎是什么?在MySQL中有什么作用?

MySQL存储引擎详解 一、术语解析 “存储”与“引擎”的汉语词典解释 1. 存储&#xff08;chǔ cn&#xff09; 汉语词典释义&#xff1a; • 动词&#xff1a; • 存放、保存&#xff08;将物品或信息放置在特定地方&#xff0c;以便后续使用&#xff09;。 ◦ 例&#xff…

测试第三课-------自动化测试相关

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

Hive null safe的用法

总结: null safe 是用<> 代表比较&#xff0c;而不是用 。null <> null 返回 true&#xff0c; 而 null null 代表 false。 NULL 和任意字符比较都返回 NULL&#xff0c;而不是 true 或者 false。如 SELECT 1 1, NULL NULL, 1 NULL;输出 true NULL NULL如果我…

LINUX基础 [四] - Linux工具

目录 软件包管理器yum Linux开发工具vim vim的基本概念 vim的三种常用模式 vim的简单配置 vim常用模式的基本操作 命令模式 底行模式 处理vim打开文件报错的问题 Linux编译器-gcc/g使用 为什么我们可以用C/C做开发呢&#xff1f; 预处理&#xff08;进行宏替换&#x…

RocketMQ 03

今天是2025/04/14 21:58 day 20 总路线请移步主页Java大纲相关文章 今天进行RocketMQ 6,7,8 个模块的归纳 最近在忙毕设&#xff0c;更新有点慢&#xff0c;见谅 首先是RocketMQ 的相关内容概括的思维导图 6. 安全机制 6.1 ACL 访问控制 核心功能 权限分级&#xff1a;通过…