文章目录
- 1.引言
- 1.1. 背景
- 1.2. 简要介绍 SOLID 原则
- 1.1. 面向对象编程和设计的重要性
- 2. 单一职责原则(SRP)
- 2.1. 定义和原理
- 2.2. SRP 的好处与目标
- 2.3. 例子和代码展示
- 2.4. 如何识别和解决 SRP 原则的违反
- 2.5. 注意事项和局限性
- 3. 开闭原则(OCP)
- 3.1. 定义和原理
- 3.2. OCP 的好处与目标
- 3.3. 例子和代码展示
- 3.4. 如何实现开闭原则:扩展、抽象、多态等
- 3.5. 注意事项和局限性
- 4.里氏替换原则(LSP)
- 4.1. 定义和原理
- 4.2. LSP的好处与目标
- 4.3. 例子和代码展示
- 4.4. 如何遵循 LSP:子类与基类的关系、重写与重载、抽象和多态等
- 4.5. 注意事项和局限性
- 5. 接口隔离原则(ISP)
- 5.1. 定义和原理
- 5.2. ISP 的好处与目标
- 5.3. 例子和代码展示
- 违反ISP的例子
- 遵循ISP的修复
- 5.4. 如何遵循 ISP:划分接口的原则、多接口与单接口的权衡等
- 5.5. 注意事项和局限性
- 6.依赖倒置原则(DIP)
- 6.1. 定义和原理
- 6.2. DIP 的好处与目标
- 6.3. 例子和代码展示
- 违反DIP的例子
- 遵循DIP的修复
- 6.4. 如何实现依赖倒置:依赖抽象、依赖注入、控制反转等
- 6.5. 注意事项和局限性
- 8. 参考资料
- SOLID原则
- 1. 单一职责原则(Single Responsibility Principle)
- 2. 开放封闭原则(Open-Closed Principle)
- 3. 里氏替换原则(Liskov Substitution Principle)
- 4. 接口隔离原则(Interface Segregation Principle)
- 5. 依赖倒置原则(Dependency Inversion Principle)
图片来源 《SOLID Principles — Concise and brief explanation》一文 https://medium.com/front-end-weekly/solid-principles-concise-and-brief-explanation-96790dc63b63
代码示例借鉴 《SOLID Principles Java》https://www.javatpoint.com/solid-principles-java
1.引言
1.1. 背景
有同学反馈最近面试被问到solid 原则。我在想什么样niubility的公司,面试已经卷到了问里氏替换原则(LSP)和依赖倒置原则(DIP)这种设计思想层面的问题了。不管咋样既然面试被问到了,说明它还是有很大的存在价值的,所以趁着周末查阅了大量文章和电子书整理了一下,从四个方面包含定义和原理、好处与目标、实现思路、以及局限性和注意事项。详细讲解一下每种原则的情况。为了方便理解我整理了一个思维导图
1.2. 简要介绍 SOLID 原则
SOLID 是一组面向对象编程和设计的原则,它们旨在引导开发者构建高质量的、可扩展的和可维护的软件。SOLID 原则包括以下五个部分:
- 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一个职责,这样可以提高类的内聚性。
- 开闭原则(Open/Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在添加新功能时,应尽量不修改现有代码,而是通过扩展的方式实现。
- 里氏替换原则(Liskov Substitution Principle, LSP):子类应该能够替换其基类,并且在替换后的代码中不会出现错误。这意味着子类应该遵循基类的行为规范,以确保在使用继承时保持代码的正确性。
- 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该被迫依赖于它不使用的接口。这意味着应该将大型接口拆分为多个小型、专用接口,以减少不必要的依赖关系。
- 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这意味着在设计软件时,应该优先考虑抽象类和接口,而不是具体实现。
1.1. 面向对象编程和设计的重要性
面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它将软件系统组织为相互交互的对象,每个对象都包含数据和对这些数据进行操作的方法。面向对象编程的主要目标是提高代码的可维护性、可扩展性和重用性。
面向对象设计(Object-Oriented Design, OOD)是在软件开发过程中定义和组织对象及其相互关系的过程。良好的面向对象设计可以帮助开发人员更好地理解问题领域,减少代码的复杂性,提高系统的可靠性和可维护性。
2. 单一职责原则(SRP)
2.1. 定义和原理
单一职责原则(Single Responsibility Principle,SRP)是SOLID原则中的一项,它指出一个类应该只有一个引起它变化的原因。换句话说,一个类应该只负责一个职责或关注点。
SRP的原理是将一个类的功能划分为若干个职责,每个职责都应该由一个单独的类来承担。这样做的好处是使类的职责更加清晰明确,提高了代码的可读性、可维护性和可测试性。
2.2. SRP 的好处与目标
SRP的使用有以下好处和目标:
-
高内聚性:单一职责原则使得类的功能集中于一个特定的职责,从而提高了类的内聚性。每个类都应该专注于完成自己的职责,这样代码更容易理解和维护。
-
低耦合性:通过将不同的职责分离到不同的类中,可以降低类之间的耦合性。当一个职责发生变化时,只需修改与之相关的类,而不会对其他类产生影响。
-
可测试性:每个类只负责一个职责,使得单元测试更加容易编写。测试可以针对单个职责进行,从而提高测试的精确性和可靠性。
-
易于扩展和重用:当需要添加新的职责时,可以创建新的类来承担该职责,而不必修改已有的类。这使得系统更加灵活,支持易于扩展和重用的设计。
2.3. 例子和代码展示
下面是一个简单的例子来说明单一职责原则:
class FileManager:
def read_file(self, file_path):
# 读取文件逻辑
def write_file(self, file_path, data):
# 写入文件逻辑
def process_data(self, data):
# 处理数据逻辑
def generate_report(self, report_data):
# 生成报告逻辑
在上述代码中,FileManager
类同时承担了文件的读写操作和数据处理、报告生成等功能。这违反了SRP原则,因为它包含了多个不同的职责。
为了遵循SRP原则,可以将不同的职责分离到不同的类中:
class FileReader:
def read_file(self, file_path):
# 读取文件逻辑
class FileWriter:
def write_file(self, file_path, data):
# 写入文件逻辑
class DataProcessor:
def process_data(self, data):
# 处理数据逻辑
class ReportGenerator:
def generate_report(self, report_data):
# 生成报告逻辑
通过将不同的职责分离到不同的类中,每个类都负责一个特定的职责,使得代码更加清晰、可维护和可扩展。
2.4. 如何识别和解决 SRP 原则的违反
以下是一些识别和解决SRP原则违反的方法:
-
职责不明确:当一个类承担了多个职责时,可以通过审查类的方法和属性来判断违反了SRP原则。如果一个类具有多个不相关的方法或属性,可能意味着它承担了多个职责,需要进行重构。
-
类的大小和复杂性:如果一个类变得过大和复杂,很可能是因为它承担了过多的职责。可以通过将不同的职责分离到不同的类中,将大类拆分为更小的类,从而解决SRP违反的问题。
-
高耦合性:如果修改一个职责导致其他职责的代码也需要修改,说明类之间存在高耦合性,违反了SRP原则。可以通过将不同职责的代码分离到独立的类中,降低耦合性。
-
单一职责的测试:如果编写测试时需要关注多个不相关的功能,可能是因为类承担了多个职责。可以通过将职责拆分到不同的类中,使得单元测试更加简单和专注。
-
违反开放封闭原则:如果为了添加新的功能而修改一个类,可能是因为它承担了多个职责。可以通过将新功能的代码放入一个新的类中,遵循开放封闭原则。
解决SRP原则违反的方法是将不同的职责分离到不同的类中。每个类应该专注于一个职责,这样可以提高代码的可读性、可维护性和可测试性。
2.5. 注意事项和局限性
尽管SRP原则有很多优点,但在实践中也需要注意以下事项和局限性:
-
抽象的平衡:将类的职责划分得过于细化可能会导致类过多,增加了代码的复杂性。需要在职责划分时找到合适的抽象级别,以确保代码的可理解性和维护性。
-
上下文相关性:有时候,类的职责可能在特定的上下文中是相关的。在这种情况下,严格遵循SRP原则可能会导致过多的类和间接性的调用。应该根据具体情况进行权衡和设计。
-
实践和经验:理解和应用SRP原则需要实践和经验。通过实际的开发项目和代码复审,开发人员可以逐渐熟悉并掌握如何划分职责和设计高内聚、低耦合的类。
综上所述,单一职责原则(SRP)是一项重要的设计原则,它鼓励将类的功能划分为单一的职责。遵循SRP可以提高代码的可读性、可维护性和可测试性,并促进代码的扩展和重用。然而,在应用SRP时需要注意抽象的平衡、上下文相关性和实践经验等因素。
3. 开闭原则(OCP)
3.1. 定义和原理
开闭原则(Open-Closed Principle,OCP) 是面向对象设计中的一个重要原则,由Bertrand Meyer在《面向对象软件构造》一书中提出。该原则指导我们设计软件实体(类、模块、函数等)时,应该对扩展开放,对修改关闭。
具体而言,开闭原则要求我们应该通过扩展现有的代码来实现新功能,而不是通过修改已有的代码。这意味着我们应该设计出易于扩展的架构,使得在添加新功能时,不需要修改已有的代码,而只需添加新的代码。
3.2. OCP 的好处与目标
开闭原则的主要目标是提高代码的可维护性、可扩展性和可复用性。以下是应用开闭原则的好处:
-
可维护性:通过遵循开闭原则,我们可以将修改的影响范围限制在扩展代码上,而不是已有的稳定代码。这样可以降低引入错误的风险,并更容易进行测试和调试。
-
可扩展性:通过添加新的代码来实现新功能,我们可以在不修改现有代码的情况下扩展系统。这样可以减少系统的耦合度,使得扩展变得更加简单和灵活。
-
可复用性:通过遵循开闭原则,我们可以将通用的功能封装成可复用的组件或模块。这样可以在不同的系统中重复使用,提高代码的复用率和开发效率。
3.3. 例子和代码展示
下面是一个简单的例子,展示如何遵循开闭原则:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
在上述例子中,Shape
是一个抽象类,定义了一个 area
方法。Rectangle
和 Circle
是具体的形状类,它们都继承自 Shape
并实现了 area
方法。
如果要添加一个新的形状,比如 Triangle
,只需创建一个新的类并继承自 Shape
,然后实现 area
方法即可,而不需要修改已有的代码。
3.4. 如何实现开闭原则:扩展、抽象、多态等
实现开闭原则的关键在于以下几个概念和技术:
-
扩展:通过添加新的代码来实现新功能,而不是修改已有的代码。新功能的实现应该以一种模块化的方式进行,以便于添加、修改和移除功能。
-
抽象:使用抽象类、接口或基类来定义稳定的公共接口。这样可以将实现细节与接口分离,让扩展代码针对接口编程,而不是具体的实现。
-
多态:通过多态性,我们可以在不同的对象上调用相同的方法,实现不同的行为。多态性使得我们可以通过接口来操作对象,而不需要关心具体的对象类型。
-
依赖倒置原则:依赖倒置原则要求我们依赖于抽象而不是具体的实现。通过依赖倒置,我们可以通过接口或抽象类来定义依赖关系,从而使得系统更加灵活和可扩展5. 设计模式:设计模式是一种常见的实现开闭原则的方法。例如,使用策略模式可以将可变的行为封装成独立的策略类,通过替换不同的策略类来实现功能的扩展,而不需要修改原有的代码。
3.5. 注意事项和局限性
在应用开闭原则时,需要注意以下事项和局限性:
-
适度的抽象:过度的抽象可能会引入不必要的复杂性。在设计时要根据实际需求和系统复杂性,找到合适的抽象级别。
-
预留扩展点:在设计时要预留一些扩展点,以便未来能够方便地添加新功能。这需要一定的设计和规划,以避免对现有代码的大规模修改。
-
不可避免的修改:尽管开闭原则鼓励我们通过扩展来实现新功能,但有些情况下仍然需要对已有的代码进行修改。在某些情况下,修改可能是合理和必要的,但应该尽量限制修改的范围和影响。
-
平衡:开闭原则需要在可扩展性和可维护性之间进行权衡。有时为了满足某种扩展需求,可能需要引入一定的复杂性和抽象层次,这可能会增加维护的难度。
总之,开闭原则是面向对象设计中的重要原则,通过遵循该原则可以提高代码的可维护性、可扩展性和可复用性。然而,实际的软件开发中需要权衡不同的因素,并根据具体情况进行灵活的设计和调整。
4.里氏替换原则(LSP)
LSP是一项重要的原则,它强调子类的替换能力,保证了代码的可靠性和可扩展性。通过合理地使用继承、重写、抽象和多态等特性,可以更好地遵循LSP原则,并设计出高质量的面向对象的代码。
4.1. 定义和原理
里氏替换原则(Liskov Substitution Principle,简称LSP)是面向对象设计的原则之一,由Barbara Liskov提出。LSP原则定义如下:
“如果S是T的子类型,那么T类型的对象可以被S类型的对象替换(即可以使用S类型的对象代替T类型的对象),而程序的逻辑行为不会受到影响。”
简而言之,LSP原则要求子类能够完全替代父类,而不会导致程序出错或产生意外的结果。子类在继承父类的同时,应该保持父类的行为规范,并且可以通过方法重写或方法扩展来增加新的行为,但不能修改父类原有的行为。
4.2. LSP的好处与目标
LSP的主要目标是增强代码的可扩展性、可维护性和可复用性。遵循LSP原则可以带来以下好处:
- 代码的可替换性:子类可以无缝替换父类,而不会影响程序的正确性。
- 代码的可扩展性:通过添加新的子类,可以扩展系统的功能,而不需要修改现有的代码。
- 代码的可维护性:由于子类保持了父类的行为规范,因此对父类的修改不会影响到已有的子类。
- 代码的可复用性:通过继承和多态,可以重用已有的代码,并通过子类的特定实现来满足不同的需求。
4.3. 例子和代码展示
以下是一个使用Java实现的简单例子,展示了违反LSP和修复后遵循LSP的情况:
我们有一个矩形类和一个正方形类。初始实现中,正方形类继承自矩形类,但违反了LSP,因为对正方形类的设置宽度和高度的方法会导致宽度和高度不相等。
// 违反LSP的例子
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int calculateArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
为了修复违反LSP的问题,我们进行了重构。我们引入了一个抽象的Shape
类,并让Rectangle
和Square
都继承该抽象类。这样,每个类都有自己的calculateArea()
方法来计算面积,而不再依赖于父类的方法。
// 遵循LSP的修复
abstract class Shape {
public abstract int calculateArea();
}
class Rectangle extends Shape {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int calculateArea() {
return width * height;
}
}
class Square extends Shape {
private int side;
public void setSide(int side) {
this.side = side;
}
public int calculateArea() {
return side * side;
}
}
4.4. 如何遵循 LSP:子类与基类的关系、重写与重载、抽象和多态等
遵循LSP的指导原则:
- 子类与基类的关系:子类应该能够替换基类,并且不会破坏程序的正确性。子类可以扩展基类的功能,但不能修改基类已有的行为。
- 重写与重载:子类可以重写基类的方法,但在重写时应该保持相同的前置条件、后置条件和约束条件,以确保替换的正确性。不应该缩小方法的前置条件,也不应该扩大方法的后置条件。
- 抽象和多态:通过使用抽象类或接口来定义通用的行为,然后通过多态性来实现在运行时选择不同的实现。这样可以使代码更灵活,可以根据需要替换具体的实现类,而不需要修改调用方的代码。
4.5. 注意事项和局限性
遵循LSP需要注意以下事项:
- 父类和子类之间的关系应该是"is-a"的关系,即子类应该是父类的一种特殊情况。如果子类不满足父类的行为约定,那么它可能不适合作为父类的替代品。
- 强制要求遵循LSP可能会导致代码中出现过度的抽象和层次结构,增加了系统的复杂性。在设计时需要权衡好可维护性和扩展性之间的平衡。
- LSP只是面向对象设计中的一个原则,它并不能解决所有设计问题。在实际应用中,还需要考虑其他原则和设计模式来达到更好的设计效果。
5. 接口隔离原则(ISP)
一句话通俗理解: ISP要求将臃肿的接口拆分为更小、更专注的接口,以避免客户端依赖于不需要的接口。
5.1. 定义和原理
接口隔离原则是面向对象设计中的一个原则,由Robert C. Martin提出。它定义为:客户端不应该强制依赖于它们不使用的接口。接口应该被划分为更小的、更具体的部分,以便客户端只需知道和使用它们所需的接口。
5.2. ISP 的好处与目标
ISP的主要目标是促进代码的灵活性、可维护性和可重用性。遵循ISP有以下好处:
- 解耦和减少依赖:通过将接口划分为更小的部分,可以减少客户端与不相关接口的依赖,从而降低耦合度。
- 提高可维护性:当需要修改接口时,只需修改与其相关的部分,而不会影响到其他部分的代码。
- 促进代码重用:通过定义更小的、更具体的接口,可以提高接口的可重用性,使其更适用于不同的场景和客户端需求。
5.3. 例子和代码展示
使用Java实现的简单例子,展示了违反ISP和修复后遵循ISP的情况:
我们有一个机器接口(Machine),定义了打印(print)、扫描(scan)和传真(fax)三个方法。然后我们有一个全能机器类(AllInOneMachine),实现了机器接口。
然而,这种设计违反了ISP,因为并非所有客户端都需要使用全部的功能。修复违反ISP的问题,我们将机器接口拆分为打印机接口(Printer)、扫描仪接口(Scanner)和传真机接口(FaxMachine)。然后我们的全能机器类实现这三个接口,这样客户端只需依赖于需要的接口,而不会强制依赖于不需要的接口。
违反ISP的例子
// 违反ISP的例子
interface Machine {
void print(); // 打印
void scan(); // 扫描
void fax(); // 传真
}
class AllInOneMachine implements Machine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
System.out.println("Scanning...");
}
@Override
public void fax() {
System.out.println("Faxing...");
}
}
遵循ISP的修复
// 遵循ISP的修复
interface Printer {
void print();
}
interface Scanner {
void scan();
}
interface FaxMachine {
void fax();
}
class AllInOneMachine implements Printer, Scanner, FaxMachine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
System.out.println("Scanning...");
}
@Override
public void fax() {
System.out.println("Faxing...");
}
}
5.4. 如何遵循 ISP:划分接口的原则、多接口与单接口的权衡等
以下是一些遵循ISP的指导原则:
- 划分接口的原则:将接口划分为更小、更具体的部分,每个接口应该代表一个单一的职责或角色。这样可以使客户端只需依赖于其需要的接口,而不会依赖于其他不需要的接口。
- 多接口与单接口的权衡:在划分接口时,需要权衡单一接口的简洁性和多接口的灵活性之间的平衡。如果单一接口足够满足大多数客户端的需求,并且没有过多的冗余方法,那么可以选择使用单一接口。但是,如果存在不同的客户端需要不同的功能子集,或者未来可能有新的功能扩展需求,那么使用多个小接口更合适。
5.5. 注意事项和局限性
在应用ISP时,需要注意以下事项和局限性:
- 避免过分细化接口:划分接口时要注意不要过分细化,导致接口数量过多或接口之间的耦合度增加。接口的划分应该有意义,并且符合系统的特定需求。
- 接口的稳定性:划分接口后,需要确保接口的稳定性和一致性。如果接口频繁变动,可能会导致客户端的改动和维护成本增加。
- 需要适度的抽象:接口应该抽象出通用的行为和功能,而不是过于具体化。过度具体化的接口可能导致客户端的依赖过于细节,降低了代码的灵活性和可重用性。
6.依赖倒置原则(DIP)
一句话通俗理解 DIP(依赖倒置原则)是指高层模块不应该依赖于低层模块,而应该依赖于抽象接口,从而使得系统各部分之间的依赖关系更加灵活,降低耦合度。DIP就是让高层和低层模块都依赖于一个统一的标准,这样就避免了它们之间的紧密联系,整个系统更容易改动和适应变化。
切记 应用依赖倒置时需要谨慎权衡,避免过度设计和引入不必要的复杂性。不要为了使用设计模式 而生搬硬套。导致画蛇添足。给原本简单的系统搞的很庞大,本来一个工具类解决的问题,制造出了8个接口和实现类。
6.1. 定义和原理
依赖倒置原则是面向对象设计中的一个原则,由Robert C. Martin提出。它定义为:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。DIP要求通过抽象来解耦高层模块和低层模块之间的依赖关系,并且依赖关系应该倒置,即高层模块和低层模块都应该依赖于抽象。
6.2. DIP 的好处与目标
DIP的主要目标是降低模块之间的耦合度,提高代码的灵活性、可扩展性和可维护性。遵循DIP有以下好处:
- 解耦模块间的依赖关系:通过引入抽象接口或抽象类,高层模块和低层模块之间的依赖关系通过抽象进行连接,而不是直接依赖于具体的实现细节。
- 提高代码的可扩展性:由于模块之间的依赖关系是通过抽象进行定义的,当需要增加新的实现时,只需实现抽象接口或继承抽象类,并注入到高层模块中,而无需修改高层模块的代码。
- 促进单元测试和模块的独立性:通过依赖抽象,可以更容易地对模块进行单元测试,因为可以使用模拟对象替代具体实现。
6.3. 例子和代码展示
我们有一个前端开发者类(FrontendDeveloper),它包含一个写JavaScript代码的方法。然后我们有一个项目类(Project),它在构造函数中创建了一个前端开发者对象,并在实现功能的方法中调用了开发者的方法。
然而,这种设计违反了DIP,因为项目类直接依赖于具体的前端开发者类。修复违反DIP的问题,我们引入了一个开发者接口(Developer),其中包含一个开发方法。我们的前端开发者类实现了该接口,并且项目类通过接口来依赖开发者,而不是具体的实现类。
违反DIP的例子
// 违反DIP的例子
class FrontendDeveloper {
void writeJavaScript() {
System.out.println("Writing JavaScript code...");
}
}
class Project {
private FrontendDeveloper developer;
Project() {
this.developer = new FrontendDeveloper();
}
void implementFeature() {
developer.writeJavaScript();
}
}
遵循DIP的修复
// 遵循DIP的修复
interface Developer {
void develop();
}
class FrontendDeveloper implements Developer {
@Override
public void develop() {
System.out.println("Writing JavaScript code...");
}
}
class Project {
private Developer developer;
Project(Developer developer) {
this.developer = developer;
}
void implementFeature() {
developer.develop();
}
}
6.4. 如何实现依赖倒置:依赖抽象、依赖注入、控制反转等
-
依赖抽象:定义抽象接口或抽象类来描述模块之间的依赖关系,高层模块和低层模块都应该依赖于抽象。通过依赖抽象,可以将模块之间的依赖关系与具体的实现细节解耦。
-
依赖注入(Dependency Injection):通过将依赖对象注入到需要它们的对象中,实现对依赖关系的解耦。依赖注入可以通过构造函数、方法参数、属性注入等方式进行。
-
控制反转(Inversion of Control):控制反转是一种实现依赖注入的方式,它将对象的创建和依赖关系的管理交给外部容器或框架来完成。通过控制反转,对象不再负责自己的依赖关系的获取和管理,而是交给外部来处理。
具体的实现方法和工具库可能因编程语言和框架而异,常见的依赖注入框架包括Spring(Java)、Dagger(Java/Kotlin)和Angular(JavaScript/TypeScript)等。
6.5. 注意事项和局限性
在实践中,使用依赖倒置原则时需要注意以下事项和局限性
抽象的设计需要考虑合理性和可维护性,过度抽象可能导致代码复杂化和不必要的抽象层次。
应该权衡依赖倒置的成本和收益。对于简单的应用程序或小规模项目,可能不需要引入复杂的依赖注入框架。
依赖注入容器的配置和管理可能需要一些额外的学习和开发成本。
依赖注入可能会增加代码的复杂性和理解难度,特别是在大型项目中,需要合理地组织和管理依赖关系。
依赖倒置并非适用于所有情况,某些特定场景下可能需要根据实际情况进行权衡和取舍。
8. 参考资料
https://zh.wikipedia.org/wiki/SOLID_(面向对象设计)
https://blog.csdn.net/zhengzhb/article/details/7296921
https://medium.com/front-end-weekly/solid-principles-concise-and-brief-explanation-96790dc63b63
SOLID原则
1. 单一职责原则(Single Responsibility Principle)
- 一个类只应该有一个引起它变化的原因。换句话说,一个类应该只有一个职责。
2. 开放封闭原则(Open-Closed Principle)
- 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即软件实体应该通过新增代码来扩展功能,而不是直接修改现有代码。
3. 里氏替换原则(Liskov Substitution Principle)
- 子类应该能够替换掉其父类并且不产生任何错误或异常。换句话说,父类中定义的行为在子类中应该保持一致。
4. 接口隔离原则(Interface Segregation Principle)
- 客户端不应该强迫依赖它们不需要的接口。应该将接口细分为更小的接口,以便客户端只需知道他们需要使用的方法。
5. 依赖倒置原则(Dependency Inversion Principle)
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体细节,具体细节应该依赖于抽象。