面向对象编程的 SOLID 原则 - SPR
SOLID 原则是面向对象 class 设计的五条原则。他们是设计 class 结构时应该遵守的准则和最佳实践。
通常,这五个原则可以帮助我们了解设计模式和软件架构。这是每个开发人员都应该了解的主题。
这篇文章介绍了在项目中使用 SOLID 原则的细节。
首先我们先看一下 SOLID 原则的历史。然后我们会设计一个 class 并且逐步改善它,来亲密接触 SOLID 原则,了解为什么使用以及怎么使用各个原则。
准备一杯咖啡或者茶,让我们马上开始!
SOLID 原则的本质
我们总算把 SOLID 原则中的五个原则说完了。但说了这么一通,好像是懂了,但是好像什么都没记住。 那么我们就来盘一盘他们之间的关系。ThoughtWorks 上有一篇文章说得挺不错,它说:
- 单一职责是所有设计原则的基础,开闭原则是设计的终极目标。
- 里氏替换原则强调的是子类替换父类后程序运行时的正确性,它用来帮助实现开闭原则。
- 而接口隔离原则用来帮助实现里氏替换原则,同时它也体现了单一职责。
- 依赖倒置原则是过程式编程与面向对象编程的分水岭,同时它也被用来指导接口隔离原则。
简单地说:依赖倒置原则告诉我们要面向接口编程。当我们面向接口编程之后,接口隔离原则和单一职责原则又告诉我们要注意职责的划分,不要什么东西都塞在一起。当我们职责捋得差不多的时候,里氏替换原则告诉我们在使用继承的时候,要注意遵守父类的约定。而上面说的这四个原则,它们的最终目标都是为了实现开闭原则。
背景
SOLID 原则首先由著名的计算机科学家 Robert C·Martin (著名的Bob大叔)由 2000 年在他的论文中提出。但是 SOLID 缩略词是稍晚由 Michael Feathers 先使用的。
Bob大叔也是畅销书《代码整洁之道》和《架构整洁之道》的作者,也是 "Agile Alliance" 的成员。
因此,代码整洁、面向对象架构、设计模式彼此互补并以这种方式连接就不足为奇了。
他们达成的目标是一致的:
“创建可多人协作的、易于理解的、易读的以及可测试的代码。”
现在依次看一下各个原则,SOLID 是以下是原则的缩写:
- S 单一职责原则
- O 开闭原则
- L 里氏替换原则
- I 接口隔离原则
- D 依赖倒置原则
单一职责原则
单一职责原则的描述是 ** 一个 class 应该只做一件事,一个 class 应该只有一个变化的原因**。
更技术的描述该原则:应该只有一个软件定义的潜在改变(数据库逻辑、日志逻辑等)能够影响 class 的定义。
这意味着如果 class 是一个数据容器,比如 Book class 或者 Student class,考虑到这个实体有一些字段,应该只有我们更改了数据定义时才能够修改这些字段。
遵守单一职责原则很重要。首先,可能很多不同的团队可能修改同一个项目,可能因为不同的原因修改同一个 class,会导致冲突。
其次,单一职责更容易版本管理,比如,有一个持久化 class 处理数据库操作,我们在 GitHub 看到某个文件上有一处修改。如果遵循 SRP 原则,根据文件就能判断这是关于存储或者数据库相关的提交。
另一个例子是合并冲突,当不同的团队修改同一个文件时,如果遵循 SRP原则,冲突很少会发生,因为文件只有一个变化的原因,即使出现冲突也会很容易解决。
常见错误和反面教材
在本节我们会看一些违背单一职责原则的常见错误。然后会探讨修复他们的方法。
我们会以一个简单的书店发票程序代码作为例子。让我们从定义一个使用发票的图书 class 开始。
class Book {
String name;
String authorName;
int year;
int price;
String isbn;
public Book(String name, String authorName, int year, int price, String isbn) {
this.name = name;
this.authorName = authorName;
this.year = year;
this.price = price;
this.isbn = isbn;
}
}
这是一个有一些字段的 book class。没什么新奇的。之所以没有把字段设置为私有的是因为想专注于逻辑而不是 getter 和 setter。
现在让我们来创建一个 invoice class,包含创建发票和计算总额的业务逻辑。目前为止,假设书店只卖书,不卖别的。
public class Invoice {
private Book book;
private int quantity;
private double discountRate;
private double taxRate;
private double total;
public Invoice(Book book, int quantity, double discountRate, double taxRate) {
this.book = book;
this.quantity = quantity;
this.discountRate = discountRate;
this.taxRate = taxRate;
this.total = this.calculateTotal();
}
public double calculateTotal() {
double price = ((book.price - book.price * discountRate) * this.quantity);
double priceWithTaxes = price * (1 + taxRate);
return priceWithTaxes;
}
public void printInvoice() {
System.out.println(quantity + "x " + book.name + " " + book.price + "$");
System.out.println("Discount Rate: " + discountRate);
System.out.println("Tax Rate: " + taxRate);
System.out.println("Total: " + total);
}
public void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}
}
这是 invoice class。它包含一些发票相关的字段以及三个方法。
- calculateTotal 方法,计算总价格
- printInvoice 方法,打印发票信息到控制台
- saveToFile 方法,负责将发票写到一个文件里
在读下一段之前停下来想一想,这样的 class 设计有什么问题。
那么问题出在哪呢? 我们的 class 在多个地方都违背了单一职责原则。
第一处是 printInvoice 方法,因为里面包含了打印逻辑。SRP 描述 class 应该只有一个变化的原因,这个变化原因应该是 class 里的发票计算。
在这个架构里,如果我们想要改变打印格式,我们需要修改这个 class。我们不能把打印逻辑和业务逻辑混合在一个class 里。
在 class 里面还有一个方法违背了 SRP: saveToFile 方法。这也是一个很常见的错误,把持久化逻辑和业务逻辑混合在了一起。
这不单单是写入文件 - 也可能是存库,发起 API 调用或者其他与持久化相关的操作。
你可能会问,怎样修复这个打印函数呢?
可以为打印和持久化逻辑创造一个新 class,因此就无需因为这些原因修改 invoice class 了。
创建两个 class, InvoicePrinter 和 InvoicePersistence ,并移入相应方法。
public class InvoicePrinter {
private Invoice invoice;
public InvoicePrinter(Invoice invoice) {
this.invoice = invoice;
}
public void print() {
System.out.println(invoice.quantity + "x " + invoice.book.name + " " + invoice.book.price + " $");
System.out.println("Discount Rate: " + invoice.discountRate);
System.out.println("Tax Rate: " + invoice.taxRate);
System.out.println("Total: " + invoice.total + " $");
}
}
public class InvoicePersistence {
Invoice invoice;
public InvoicePersistence(Invoice invoice) {
this.invoice = invoice;
}
public void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}
}
现在 class 结构遵从了单一职责原则,每个 class 为我们应用的一个部分负责。棒!
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/17590035.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
2022-07-29 智能机器人方案
2022-07-29 这4个方法将提高你的理解力,成为一个超强学习达人!
2019-07-29 Crond服务+Shell实现秒级任务
2019-07-29 全栈工程师
2019-07-29 nginx配置https后,网站出现无法访问情况
2019-07-29 shell文件报错syntax error near unexpected token '$'\r''