|
|
@@ -1,19 +1,905 @@
|
|
|
+---
|
|
|
title: 架构师学习-SOLID原则
|
|
|
author: Gamehu
|
|
|
date: 2025-12-12 22:43:25
|
|
|
tags:
|
|
|
- SOLID
|
|
|
+ - 架构设计
|
|
|
+ - 设计模式
|
|
|
categories:
|
|
|
- - 架构师
|
|
|
+ - 架构师
|
|
|
---
|
|
|
- <div class="tag-container">
|
|
|
+<div class="tag-container">
|
|
|
<span class="ai-tag">架构师学习</span>
|
|
|
<span class="sub-tag">第4篇</span>
|
|
|
</div>
|
|
|
|
|
|
+## 引言
|
|
|
+
|
|
|
+<div class="article-quote">
|
|
|
+SOLID原则是由Robert C. Martin(Uncle Bob)提出的五个面向对象设计原则,这些原则帮助我们编写更加<span class="highlight-text">可维护、可扩展、可测试</span>的代码。作为架构师,理解和应用这些原则是必不可少的基本功。
|
|
|
+</div>
|
|
|
+
|
|
|
+在软件开发的早期,我们往往更关注功能的实现,而忽视了代码的设计质量。随着项目规模的增长,<span class="warning">糟糕的代码设计</span>会变得越来越难以维护,每一次修改都可能引发意想不到的问题。SOLID原则正是为了解决这些问题而诞生的。
|
|
|
+
|
|
|
+### 为什么要学习SOLID原则?
|
|
|
+
|
|
|
+<span class="gradient-text">SOLID原则能够帮助我们:</span>
|
|
|
+
|
|
|
+1. **降低代码耦合度**:让模块之间的依赖关系更加清晰
|
|
|
+2. **提高代码可维护性**:单一职责让修改更加聚焦
|
|
|
+3. **增强代码可扩展性**:开闭原则让功能扩展更安全
|
|
|
+4. **改善代码可测试性**:依赖倒置让单元测试更容易编写
|
|
|
|
|
|
### 学习资料
|
|
|
|
|
|
-https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
|
|
|
-https://www.reddit.com/r/cscareerquestions/comments/13otvbb/could_you_explain_the_5_solid_principles_off_the/?tl=zh-hans
|
|
|
-https://www.explainthis.io/zh-hans/swe/solid
|
|
|
+- [DigitalOcean: S.O.L.I.D The First Five Principles](https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design)
|
|
|
+- [ExplainThis: 什么是SOLID原则](https://www.explainthis.io/zh-hans/swe/solid)
|
|
|
+
|
|
|
+<div class="divider-gradient"></div>
|
|
|
+
|
|
|
+## 单一职责原则(Single Responsibility Principle,SRP)
|
|
|
+
|
|
|
+### 原则定义
|
|
|
+
|
|
|
+> **一个类应该只有一个引起它变化的原因**,换句话说,一个类应该只有一个职责。
|
|
|
+
|
|
|
+### 直观理解
|
|
|
+
|
|
|
+想象一个瑞士军刀,它有太多功能:刀、剪刀、开瓶器等。虽然功能强大,但当你只需要用刀的时候,带着整个军刀就显得笨重了。同样,一个类承担太多职责时,任何职责的变化都可能影响其他职责,导致系统变得脆弱。
|
|
|
+
|
|
|
+### 反例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 违反单一职责原则的用户服务类
|
|
|
+ * 该类同时负责用户业务逻辑和日志记录
|
|
|
+ */
|
|
|
+public class UserService {
|
|
|
+
|
|
|
+ // 职责1:用户管理
|
|
|
+ public void register(String username, String password) {
|
|
|
+ // 注册逻辑
|
|
|
+ System.out.println("用户注册:" + username);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void login(String username, String password) {
|
|
|
+ // 登录逻辑
|
|
|
+ System.out.println("用户登录:" + username);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 职责2:日志记录 - 这不应该属于用户服务
|
|
|
+ public void logToFile(String message) {
|
|
|
+ try {
|
|
|
+ FileWriter writer = new FileWriter("app.log", true);
|
|
|
+ writer.write(message + "\n");
|
|
|
+ writer.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 职责3:邮件发送 - 这也不应该属于用户服务
|
|
|
+ public void sendEmail(String to, String subject, String content) {
|
|
|
+ System.out.println("发送邮件给:" + to);
|
|
|
+ System.out.println("主题:" + subject);
|
|
|
+ System.out.println("内容:" + content);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="warning">
|
|
|
+问题分析:
|
|
|
+1. UserService类承担了用户管理、日志记录、邮件发送三个职责
|
|
|
+2. 修改日志格式需要修改UserService
|
|
|
+3. 更换邮件服务提供商需要修改UserService
|
|
|
+4. 这违反了单一职责原则,导致类的变化原因过多
|
|
|
+</div>
|
|
|
+
|
|
|
+### 正例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 符合单一职责原则的用户服务类
|
|
|
+ * 只负责用户相关的业务逻辑
|
|
|
+ */
|
|
|
+public class UserService {
|
|
|
+ private Logger logger;
|
|
|
+ private EmailService emailService;
|
|
|
+
|
|
|
+ public UserService(Logger logger, EmailService emailService) {
|
|
|
+ this.logger = logger;
|
|
|
+ this.emailService = emailService;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只关注用户管理职责
|
|
|
+ public void register(String username, String password) {
|
|
|
+ logger.log("用户注册:" + username);
|
|
|
+ // 注册逻辑
|
|
|
+ emailService.sendEmail(username, "注册成功", "欢迎注册");
|
|
|
+ }
|
|
|
+
|
|
|
+ public void login(String username, String password) {
|
|
|
+ logger.log("用户登录:" + username);
|
|
|
+ // 登录逻辑
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 专门的日志服务类
|
|
|
+ */
|
|
|
+public class Logger {
|
|
|
+ public void log(String message) {
|
|
|
+ try {
|
|
|
+ FileWriter writer = new FileWriter("app.log", true);
|
|
|
+ writer.write(message + "\n");
|
|
|
+ writer.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 专门的邮件服务类
|
|
|
+ */
|
|
|
+public class EmailService {
|
|
|
+ public void sendEmail(String to, String subject, String content) {
|
|
|
+ System.out.println("发送邮件给:" + to);
|
|
|
+ System.out.println("主题:" + subject);
|
|
|
+ System.out.println("内容:" + content);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="card">
|
|
|
+重构收益:
|
|
|
+1. 每个类都有明确的单一职责
|
|
|
+2. 修改日志实现只需修改Logger类
|
|
|
+3. 更换邮件服务只需修改EmailService类
|
|
|
+4. UserService类保持稳定,不受其他职责变化影响
|
|
|
+</div>
|
|
|
+
|
|
|
+<div class="divider-wave"></div>
|
|
|
+
|
|
|
+## 开闭原则(Open-Closed Principle,OCP)
|
|
|
+
|
|
|
+### 原则定义
|
|
|
+
|
|
|
+> **软件实体(类、模块、函数等)应该对扩展开放,对修改封闭**。
|
|
|
+
|
|
|
+这意味着当我们需要添加新功能时,应该通过扩展现有代码来实现,而不是修改已有的代码。
|
|
|
+
|
|
|
+### 直观理解
|
|
|
+
|
|
|
+想象一个插线板,它有多个插座。当你需要使用新电器时,你只需要插上新的插头,而不需要拆开插线板重新布线。开闭原则就是让我们的代码像插线板一样,能够轻松"插入"新功能而不需要修改核心代码。
|
|
|
+
|
|
|
+### 反例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 违反开闭原则的折扣计算器
|
|
|
+ * 每次新增折扣类型都需要修改这个类
|
|
|
+ */
|
|
|
+public class DiscountCalculator {
|
|
|
+
|
|
|
+ public double calculateDiscount(String discountType, double price) {
|
|
|
+ switch (discountType) {
|
|
|
+ case "NONE":
|
|
|
+ return price;
|
|
|
+ case "TEN_PERCENT":
|
|
|
+ return price * 0.9;
|
|
|
+ case "TWENTY_PERCENT":
|
|
|
+ return price * 0.8;
|
|
|
+ // 每次新增折扣类型都需要在这里添加新的case
|
|
|
+ // 这违反了开闭原则
|
|
|
+ default:
|
|
|
+ return price;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="warning">
|
|
|
+违反开闭原则的后果:
|
|
|
+1. 每次添加新的折扣类型都需要修改DiscountCalculator类
|
|
|
+2. 修改已有代码可能引入新的bug
|
|
|
+3. 需要重新测试整个折扣计算功能
|
|
|
+4. 违反了对修改封闭的原则
|
|
|
+</div>
|
|
|
+
|
|
|
+### 正例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 折扣策略接口
|
|
|
+ */
|
|
|
+public interface DiscountStrategy {
|
|
|
+ double calculate(double price);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 无折扣策略
|
|
|
+ */
|
|
|
+public class NoDiscount implements DiscountStrategy {
|
|
|
+ @Override
|
|
|
+ public double calculate(double price) {
|
|
|
+ return price;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 10%折扣策略
|
|
|
+ */
|
|
|
+public class TenPercentDiscount implements DiscountStrategy {
|
|
|
+ @Override
|
|
|
+ public double calculate(double price) {
|
|
|
+ return price * 0.9;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 20%折扣策略
|
|
|
+ */
|
|
|
+public class TwentyPercentDiscount implements DiscountStrategy {
|
|
|
+ @Override
|
|
|
+ public double calculate(double price) {
|
|
|
+ return price * 0.8;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 符合开闭原则的折扣计算器
|
|
|
+ */
|
|
|
+public class DiscountCalculator {
|
|
|
+ private DiscountStrategy discountStrategy;
|
|
|
+
|
|
|
+ public DiscountCalculator(DiscountStrategy discountStrategy) {
|
|
|
+ this.discountStrategy = discountStrategy;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double calculate(double price) {
|
|
|
+ return discountStrategy.calculate(price);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可以动态切换折扣策略
|
|
|
+ public void setDiscountStrategy(DiscountStrategy discountStrategy) {
|
|
|
+ this.discountStrategy = discountStrategy;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+public class Main {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ DiscountCalculator calculator = new DiscountCalculator(new NoDiscount());
|
|
|
+ System.out.println("无折扣价格:" + calculator.calculate(100));
|
|
|
+
|
|
|
+ // 切换到10%折扣
|
|
|
+ calculator.setDiscountStrategy(new TenPercentDiscount());
|
|
|
+ System.out.println("10%折扣后价格:" + calculator.calculate(100));
|
|
|
+
|
|
|
+ // 切换到20%折扣
|
|
|
+ calculator.setDiscountStrategy(new TwentyPercentDiscount());
|
|
|
+ System.out.println("20%折扣后价格:" + calculator.calculate(100));
|
|
|
+
|
|
|
+ // 添加新的折扣类型只需创建新的策略类,无需修改DiscountCalculator
|
|
|
+ calculator.setDiscountStrategy(new ThirtyPercentDiscount());
|
|
|
+ System.out.println("30%折扣后价格:" + calculator.calculate(100));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 新增30%折扣策略 - 无需修改任何现有代码
|
|
|
+ */
|
|
|
+public class ThirtyPercentDiscount implements DiscountStrategy {
|
|
|
+ @Override
|
|
|
+ public double calculate(double price) {
|
|
|
+ return price * 0.7;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="card">
|
|
|
+重构收益:
|
|
|
+1. 添加新的折扣类型只需创建新的策略类
|
|
|
+2. DiscountCalculator类无需修改,符合开闭原则
|
|
|
+3. 通过策略模式实现了对扩展开放、对修改封闭
|
|
|
+4. 每个折扣策略都是独立的,易于测试和维护
|
|
|
+</div>
|
|
|
+
|
|
|
+<div class="divider-tech"></div>
|
|
|
+
|
|
|
+## 里氏替换原则(Liskov Substitution Principle,LSP)
|
|
|
+
|
|
|
+### 原则定义
|
|
|
+
|
|
|
+> **所有引用基类的地方必须能够透明地使用其子类的对象**,子类可以替换父类出现在父类能够出现的任何地方,而不破坏程序的正确性。
|
|
|
+
|
|
|
+### 直观理解
|
|
|
+
|
|
|
+如果你有一个正方形和一个长方形,从几何上讲,正方形是特殊的长方形。但在编程中,如果让正方形继承长方形类,可能会出现问题。因为正方形的长宽必须相等,这违反了长方形"长宽可以不同"的基本约定。
|
|
|
+
|
|
|
+### 反例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 长方形类
|
|
|
+ */
|
|
|
+public class Rectangle {
|
|
|
+ protected double width;
|
|
|
+ protected double height;
|
|
|
+
|
|
|
+ public Rectangle(double width, double height) {
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWidth(double width) {
|
|
|
+ this.width = width;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setHeight(double height) {
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getWidth() {
|
|
|
+ return width;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getHeight() {
|
|
|
+ return height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public double getArea() {
|
|
|
+ return width * height;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 正方形类继承长方形
|
|
|
+ * 这违反了里氏替换原则
|
|
|
+ */
|
|
|
+public class Square extends Rectangle {
|
|
|
+
|
|
|
+ public Square(double size) {
|
|
|
+ super(size, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setWidth(double width) {
|
|
|
+ this.width = width;
|
|
|
+ this.height = width; // 正方形长宽必须相等
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setHeight(double height) {
|
|
|
+ this.width = height; // 正方形长宽必须相等
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 测试类
|
|
|
+ */
|
|
|
+public class LSPTest {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ Rectangle rectangle = new Rectangle(5, 10);
|
|
|
+ System.out.println("长方形面积:" + rectangle.getArea()); // 50
|
|
|
+
|
|
|
+ rectangle.setWidth(10);
|
|
|
+ rectangle.setHeight(5);
|
|
|
+ System.out.println("长方形面积:" + rectangle.getArea()); // 50
|
|
|
+
|
|
|
+ // 使用正方形替换长方形
|
|
|
+ Rectangle square = new Square(5);
|
|
|
+ System.out.println("正方形面积:" + square.getArea()); // 25
|
|
|
+
|
|
|
+ square.setWidth(10);
|
|
|
+ square.setHeight(5);
|
|
|
+ System.out.println("正方形面积:" + square.getArea()); // 25, 而不是50!
|
|
|
+
|
|
|
+ // 这里违反了里氏替换原则
|
|
|
+ // 当使用Square替换Rectangle时,程序的行为发生了变化
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="warning">
|
|
|
+违反里氏替换原则的问题:
|
|
|
+1. Square继承Rectangle后,破坏了Rectangle的行为约定
|
|
|
+2. 当使用Square替换Rectangle时,程序结果不一致
|
|
|
+3. 客户端代码无法正确预测继承后的行为
|
|
|
+4. 这种继承关系在设计上就是错误的
|
|
|
+</div>
|
|
|
+
|
|
|
+### 正例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 形状接口
|
|
|
+ */
|
|
|
+public interface Shape {
|
|
|
+ double getArea();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 长方形类实现形状接口
|
|
|
+ */
|
|
|
+public class Rectangle implements Shape {
|
|
|
+ private double width;
|
|
|
+ private double height;
|
|
|
+
|
|
|
+ public Rectangle(double width, double height) {
|
|
|
+ this.width = width;
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWidth(double width) {
|
|
|
+ this.width = width;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setHeight(double height) {
|
|
|
+ this.height = height;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public double getArea() {
|
|
|
+ return width * height;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 正方形类实现形状接口
|
|
|
+ */
|
|
|
+public class Square implements Shape {
|
|
|
+ private double size;
|
|
|
+
|
|
|
+ public Square(double size) {
|
|
|
+ this.size = size;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSize(double size) {
|
|
|
+ this.size = size;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public double getArea() {
|
|
|
+ return size * size;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 形状工具类 - 可以处理任何实现Shape接口的对象
|
|
|
+ */
|
|
|
+public class ShapeUtils {
|
|
|
+ public static void printArea(Shape shape) {
|
|
|
+ System.out.println("形状面积:" + shape.getArea());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 测试类
|
|
|
+ */
|
|
|
+public class LSPTestCorrect {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ Rectangle rectangle = new Rectangle(5, 10);
|
|
|
+ Square square = new Square(5);
|
|
|
+
|
|
|
+ // Rectangle和Square可以透明替换
|
|
|
+ ShapeUtils.printArea(rectangle); // 形状面积:50.0
|
|
|
+ ShapeUtils.printArea(square); // 形状面积:25.0
|
|
|
+
|
|
|
+ // 使用多态
|
|
|
+ List<Shape> shapes = Arrays.asList(rectangle, square);
|
|
|
+ for (Shape shape : shapes) {
|
|
|
+ ShapeUtils.printArea(shape);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="card">
|
|
|
+重构收益:
|
|
|
+1. Rectangle和Square都实现Shape接口,各自保持独立性
|
|
|
+2. 任何使用Shape的地方都可以透明地使用Rectangle或Square
|
|
|
+3. 避免了不合理的继承关系
|
|
|
+4. 符合里氏替换原则,保证程序行为的正确性
|
|
|
+</div>
|
|
|
+
|
|
|
+<div class="divider-stars"></div>
|
|
|
+
|
|
|
+## 接口隔离原则(Interface Segregation Principle,ISP)
|
|
|
+
|
|
|
+### 原则定义
|
|
|
+
|
|
|
+> **客户端不应该被迫依赖于它不使用的接口**,接口应该被拆分为更小和更具体的部分,这样客户端只需要知道它们所需的部分。
|
|
|
+
|
|
|
+### 直观理解
|
|
|
+
|
|
|
+想象一个万能遥控器,上面有电视、空调、音响等各种设备的按钮。当你只需要控制电视时,面对这么多无关的按钮会很困扰。接口隔离原则就是让每个接口都专注于特定的功能,避免"胖接口"。
|
|
|
+
|
|
|
+### 反例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 臃肿的机器接口 - 包含所有可能的操作
|
|
|
+ */
|
|
|
+public interface Machine {
|
|
|
+ void print(String document);
|
|
|
+ void fax(String document);
|
|
|
+ void scan(String document);
|
|
|
+ void photocopy(String document);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 打印机类 - 被迫实现不需要的方法
|
|
|
+ */
|
|
|
+public class OldPrinter implements Machine {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void print(String document) {
|
|
|
+ System.out.println("打印:" + document);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void fax(String document) {
|
|
|
+ // 老式打印机不支持传真,但被迫实现这个方法
|
|
|
+ throw new UnsupportedOperationException("不支持传真功能");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void scan(String document) {
|
|
|
+ // 老式打印机不支持扫描,但被迫实现这个方法
|
|
|
+ throw new UnsupportedOperationException("不支持扫描功能");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void photocopy(String document) {
|
|
|
+ // 老式打印机不支持复印,但被迫实现这个方法
|
|
|
+ throw new UnsupportedOperationException("不支持复印功能");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="warning">
|
|
|
+违反接口隔离原则的问题:
|
|
|
+1. Machine接口过于臃肿,包含了太多操作
|
|
|
+2. OldPrinter只需要打印功能,但被迫实现其他方法
|
|
|
+3. 客户端可能调用不支持的方法,导致运行时异常
|
|
|
+4. 接口设计不够灵活,无法适应不同的设备组合
|
|
|
+</div>
|
|
|
+
|
|
|
+### 正例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 打印接口
|
|
|
+ */
|
|
|
+public interface Printer {
|
|
|
+ void print(String document);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 传真接口
|
|
|
+ */
|
|
|
+public interface Fax {
|
|
|
+ void fax(String document);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 扫描接口
|
|
|
+ */
|
|
|
+public interface Scanner {
|
|
|
+ void scan(String document);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 复印接口
|
|
|
+ */
|
|
|
+public interface Photocopier {
|
|
|
+ void photocopy(String document);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 简单打印机 - 只实现打印功能
|
|
|
+ */
|
|
|
+public class SimplePrinter implements Printer {
|
|
|
+ @Override
|
|
|
+ public void print(String document) {
|
|
|
+ System.out.println("打印:" + document);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 多功能打印机 - 实现多个接口
|
|
|
+ */
|
|
|
+public class MultiFunctionPrinter implements Printer, Fax, Scanner, Photocopier {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void print(String document) {
|
|
|
+ System.out.println("打印:" + document);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void fax(String document) {
|
|
|
+ System.out.println("传真:" + document);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void scan(String document) {
|
|
|
+ System.out.println("扫描:" + document);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void photocopy(String document) {
|
|
|
+ System.out.println("复印:" + document);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 打印扫描一体机 - 只实现需要的接口
|
|
|
+ */
|
|
|
+public class PrintScanCombo implements Printer, Scanner {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void print(String document) {
|
|
|
+ System.out.println("打印:" + document);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void scan(String document) {
|
|
|
+ System.out.println("扫描:" + document);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+public class ISPTest {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ // 简单打印机只需要Printer接口
|
|
|
+ Printer simplePrinter = new SimplePrinter();
|
|
|
+ simplePrinter.print("简单文档");
|
|
|
+
|
|
|
+ // 多功能打印机可以实现所有功能
|
|
|
+ MultiFunctionPrinter mfp = new MultiFunctionPrinter();
|
|
|
+ usePrinter(mfp);
|
|
|
+ useFax(mfp);
|
|
|
+ useScanner(mfp);
|
|
|
+
|
|
|
+ // 打印扫描一体机只需要打印和扫描功能
|
|
|
+ PrintScanCombo combo = new PrintScanCombo();
|
|
|
+ usePrinter(combo);
|
|
|
+ useScanner(combo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 客户端只需要依赖它需要的接口
|
|
|
+ public static void usePrinter(Printer printer) {
|
|
|
+ printer.print("使用打印机");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void useFax(Fax fax) {
|
|
|
+ fax.fax("使用传真机");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void useScanner(Scanner scanner) {
|
|
|
+ scanner.scan("使用扫描仪");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="card">
|
|
|
+重构收益:
|
|
|
+1. 接口被拆分为多个小而专注的接口
|
|
|
+2. 客户端只依赖它需要的接口,避免不必要的方法
|
|
|
+3. 实现类可以选择性地实现需要的接口
|
|
|
+4. 符合接口隔离原则,提高了系统的灵活性和可维护性
|
|
|
+</div>
|
|
|
+
|
|
|
+<div class="divider-gradient"></div>
|
|
|
+
|
|
|
+## 依赖倒置原则(Dependency Inversion Principle,DIP)
|
|
|
+
|
|
|
+### 原则定义
|
|
|
+
|
|
|
+> **高层模块不应该依赖低层模块,两者都应该依赖抽象**。抽象不应该依赖细节,细节应该依赖抽象。
|
|
|
+
|
|
|
+### 直观理解
|
|
|
+
|
|
|
+想象一个公司组织架构,总经理(高层)不应该直接管理员工(低层)的每一个具体工作。相反,总经理应该制定标准和接口,员工按照这些标准工作。这样,更换员工不会影响公司的整体运作。
|
|
|
+
|
|
|
+### 反例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * MySQL数据库连接类 - 低层模块
|
|
|
+ */
|
|
|
+public class MySQLConnection {
|
|
|
+ public void connect() {
|
|
|
+ System.out.println("连接到MySQL数据库");
|
|
|
+ }
|
|
|
+
|
|
|
+ public void executeQuery(String sql) {
|
|
|
+ System.out.println("在MySQL中执行查询:" + sql);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 用户服务类 - 高层模块
|
|
|
+ * 直接依赖具体的MySQLConnection
|
|
|
+ */
|
|
|
+public class UserService {
|
|
|
+ private MySQLConnection dbConnection;
|
|
|
+
|
|
|
+ public UserService() {
|
|
|
+ this.dbConnection = new MySQLConnection(); // 高层依赖低层具体实现
|
|
|
+ }
|
|
|
+
|
|
|
+ public void getUser(String userId) {
|
|
|
+ dbConnection.connect();
|
|
|
+ dbConnection.executeQuery("SELECT * FROM users WHERE id = " + userId);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="warning">
|
|
|
+违反依赖倒置原则的问题:
|
|
|
+1. UserService直接依赖MySQLConnection具体类
|
|
|
+2. 如果要更换数据库(如PostgreSQL),需要修改UserService
|
|
|
+3. 高层模块被低层模块的具体实现所束缚
|
|
|
+4. 难以进行单元测试(无法mock数据库连接)
|
|
|
+</div>
|
|
|
+
|
|
|
+### 正例代码
|
|
|
+
|
|
|
+```java
|
|
|
+/**
|
|
|
+ * 数据库连接接口 - 抽象层
|
|
|
+ */
|
|
|
+public interface DatabaseConnection {
|
|
|
+ void connect();
|
|
|
+ void executeQuery(String sql);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * MySQL数据库连接实现
|
|
|
+ */
|
|
|
+public class MySQLConnection implements DatabaseConnection {
|
|
|
+ @Override
|
|
|
+ public void connect() {
|
|
|
+ System.out.println("连接到MySQL数据库");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void executeQuery(String sql) {
|
|
|
+ System.out.println("在MySQL中执行查询:" + sql);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * PostgreSQL数据库连接实现
|
|
|
+ */
|
|
|
+public class PostgreSQLConnection implements DatabaseConnection {
|
|
|
+ @Override
|
|
|
+ public void connect() {
|
|
|
+ System.out.println("连接到PostgreSQL数据库");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void executeQuery(String sql) {
|
|
|
+ System.out.println("在PostgreSQL中执行查询:" + sql);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * MongoDB数据库连接实现
|
|
|
+ */
|
|
|
+public class MongoDBConnection implements DatabaseConnection {
|
|
|
+ @Override
|
|
|
+ public void connect() {
|
|
|
+ System.out.println("连接到MongoDB数据库");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void executeQuery(String sql) {
|
|
|
+ System.out.println("在MongoDB中执行查询:" + sql);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 符合依赖倒置原则的用户服务类
|
|
|
+ * 依赖抽象接口,而不是具体实现
|
|
|
+ */
|
|
|
+public class UserService {
|
|
|
+ private DatabaseConnection dbConnection;
|
|
|
+
|
|
|
+ // 通过构造器注入依赖
|
|
|
+ public UserService(DatabaseConnection dbConnection) {
|
|
|
+ this.dbConnection = dbConnection; // 依赖抽象而非具体实现
|
|
|
+ }
|
|
|
+
|
|
|
+ public void getUser(String userId) {
|
|
|
+ dbConnection.connect();
|
|
|
+ dbConnection.executeQuery("SELECT * FROM users WHERE id = " + userId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 也可以通过setter注入
|
|
|
+ public void setDbConnection(DatabaseConnection dbConnection) {
|
|
|
+ this.dbConnection = dbConnection;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+public class DIPTest {
|
|
|
+ public static void main(String[] args) {
|
|
|
+ // 可以轻松切换不同的数据库实现
|
|
|
+ DatabaseConnection mysqlConnection = new MySQLConnection();
|
|
|
+ DatabaseConnection pgConnection = new PostgreSQLConnection();
|
|
|
+ DatabaseConnection mongoConnection = new MongoDBConnection();
|
|
|
+
|
|
|
+ UserService userService1 = new UserService(mysqlConnection);
|
|
|
+ userService1.getUser("1");
|
|
|
+
|
|
|
+ UserService userService2 = new UserService(pgConnection);
|
|
|
+ userService2.getUser("2");
|
|
|
+
|
|
|
+ UserService userService3 = new UserService(mongoConnection);
|
|
|
+ userService3.getUser("3");
|
|
|
+
|
|
|
+ // 运行时切换数据库实现
|
|
|
+ userService1.setDbConnection(mongoConnection);
|
|
|
+ userService1.getUser("4");
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+<div class="card">
|
|
|
+重构收益:
|
|
|
+1. UserService依赖DatabaseConnection抽象接口,而非具体实现
|
|
|
+2. 可以轻松切换不同的数据库实现,无需修改UserService
|
|
|
+3. 高层模块和低层模块都依赖抽象,降低了耦合度
|
|
|
+4. 便于单元测试,可以轻松mock DatabaseConnection
|
|
|
+</div>
|
|
|
+
|
|
|
+## 总结
|
|
|
+
|
|
|
+<div class="article-quote">
|
|
|
+SOLID原则是面向对象设计的基石,它们相互关联、相互补充。掌握这些原则能够帮助我们设计出更加优雅、灵活、可维护的软件系统。
|
|
|
+</div>
|
|
|
+
|
|
|
+### SOLID原则的关系图
|
|
|
+
|
|
|
+```
|
|
|
+┌─────────────────────────────────────────────────┐
|
|
|
+│ SOLID 原则 │
|
|
|
+├─────────────────────────────────────────────────┤
|
|
|
+│ │
|
|
|
+│ SRP ──────── 单一职责 ───────── 一个类只有一个职责 │
|
|
|
+│ ↓ │
|
|
|
+│ OCP ──────── 开闭原则 ────── 对扩展开放,对修改封闭 │
|
|
|
+│ ↓ │
|
|
|
+│ LSP ──────── 里氏替换 ──── 子类可以透明替换父类 │
|
|
|
+│ ↓ │
|
|
|
+│ ISP ──────── 接口隔离 ──── 接口应该小而专注 │
|
|
|
+│ ↓ │
|
|
|
+│ DIP ──────── 依赖倒置 ── 依赖抽象而非具体实现 │
|
|
|
+│ │
|
|
|
+└─────────────────────────────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+### 实践建议
|
|
|
+
|
|
|
+<span class="gradient-text">如何在项目中应用SOLID原则:</span>
|
|
|
+
|
|
|
+1. **逐步重构**:不要试图一次性重构所有代码,逐步应用这些原则
|
|
|
+2. **识别坏味道**:学会识别违反SOLID原则的代码坏味道
|
|
|
+3. **设计优先**:在编写新代码时,优先考虑SOLID原则
|
|
|
+4. **团队共识**:确保团队成员都理解并认同这些原则
|
|
|
+5. **适度应用**:不要过度设计,根据实际情况灵活应用
|
|
|
+
|
|
|
+<div class="important-note">
|
|
|
+重要提示:
|
|
|
+SOLID原则是指导原则,不是绝对的规则。在实际项目中,需要根据具体情况灵活应用。过度遵循这些原则可能导致过度设计,增加系统复杂度。
|
|
|
+</div>
|
|
|
+
|
|
|
+<div class="divider-stars"></div>
|
|
|
+
|
|
|
+<span class="ai-tag">架构师学习系列</span> 敬请期待下一篇
|