在本教程中,我们将讨论面向对象设计的SOLID原则。 首先,我们将首先探讨它们出现的原因以及为什么在设计软件时应该考虑它们。然后,我们将概述每个原则以及一些示例代码以强调这一点。
SOLID原则的原因 SOLID原则首先由Robert C. Martin在2000年的论文“ 设计原则和设计模式”中概念化 。 这些概念后来由Michael Feathers构建,他们向我们介绍了SOLID的首字母缩略词。在过去的20年中,这5个原则彻底改变了面向对象编程的世界,改变了我们编写软件的方式。 那么,什么是SOLID以及它如何帮助我们编写更好的代码?简而言之,Martin和Feathers的设计原则鼓励我们创建更易于维护,易懂且灵活的软件。因此,随着我们的应用程序规模不断扩大,我们可以降低其复杂性,并为您节省更多的麻烦!
以下5个概念构成了我们的SOLID原则:
- Single Responsibility单一职责
- Open/Closed开闭原则
- Liskov Substitution
- Interface Segregation接口分离
- Dependency Injection依赖注射
1. 单一责任 让我们用单一的责任原则来解决问题。正如我们所预料的那样,这个原则规定一个类应该只有一个责任。此外,它只应该有一个改变的理由。 这个原则如何帮助我们构建更好的软件?让我们看看它的一些好处:
- 测试 - 具有一个职责的类将具有少得多的测试用例
- 较低的耦合 - 单个类中较少的功能将具有较少的依赖性
- 组织 - 较小,组织良好的类比单片类更容易搜索
public class Book { private String name; private String author; private String text; //constructor, getters and setters } |
public class Book { private String name; private String author; private String text; //constructor, getters and setters // methods that directly relate to the book properties public String replaceWordInText(String word){ return text.replaceAll(word, text); } public boolean isWordInText(String word){ return text.contains(word); } } |
public class Book { //... void printTextToConsole(){ // our code for formatting and printing the text } } |
但是,此代码违反了我们之前概述的单一责任原则。为了修复我们的混乱,我们应该实现一个单独的类,只关注打印我们的文本:
public class BookPrinter { // methods for outputting text void printTextToConsole(String text){ //our code for formatting and printing the text } void printTextToAnotherMedium(String text){ // code for writing to any other location.. } } |
2. 开闭原则 类应该是可以扩展的,但是对修改是关闭, 我们应该停止修改现有代码,否则会在经过测试的应用程序中引发潜在的新错误。当然,规则的一个例外是:修复现有代码中的错误。 让我们通过快速代码示例进一步探索这个概念,作为新项目的一部分,想象一下我们已经实现了一个 吉他 类。
public class Guitar { private String make; private String model; private int volume; //Constructors, getters & setters } |
public class SuperCoolGuitarWithFlames extends Guitar { private String flameColor; //constructor, getters + setters } |
通过扩展 Guitar 类,我们可以确保我们现有的应用程序不会受到影响。
3. Liskov替代 这可能是5个原则中最复杂的。简单地说,如果A类是B类的子类型,那么我们应该能够用 A 替换 B 而不破坏我们程序的行为。让我们直接跳转到代码,以帮助我们围绕这个概念:
public interface Car { void turnOnEngine(); void accelerate(); } |
public class MotorCar implements Car { private Engine engine; //Constructors, getters + setters public void turnOnEngine() { //turn on the engine! engine.on(); } public void accelerate() { //move forward! engine.powerOn(1000); } } |
正如我们的代码描述的那样,我们有一个可以打开的引擎,我们可以增加功率。但等一下,2019年,埃隆马斯克一直是一个忙碌的人。 我们现在生活在电动汽车时代:
public class ElectricCar implements Car { public void turnOnEngine() { throw new AssertionError("I don't have an engine!"); } public void accelerate() { //this acceleration is crazy! } } |
4.接口隔离 SOLID中的“I”代表接口隔离,它只是意味着更大的接口应该分成更小的接口。通过这样做,我们可以确保实现类只需要关注它们感兴趣的方法。 对于这个例子,我们将尝试作为动物园管理员。更具体地说,我们将在熊圈中工作。 让我们从一个界面开始,概述我们作为熊守护者的角色:
public interface BearKeeper { void washTheBear(); void feedTheBear(); void petTheBear(); } |
public interface BearCleaner { void washTheBear(); } public interface BearFeeder { void feedTheBear(); } public interface BearPetter { void petTheBear(); } |
public class BearCarer implements BearCleaner, BearFeeder { public void washTheBear() { //I think we missed a spot... } public void feedTheBear() { //Tuna Tuesdays... } } |
最后,我们可以把危险的东西留给疯狂的人:
public class CrazyPerson implements BearPetter { public void petTheBear() { //Good luck with that! } } |
更进一步,我们甚至可以将我们的BookPrinter 类从之前的示例拆分 为以相同的方式使用接口隔离。通过使用单个打印 方法实现 Printer接口 ,我们可以实例化单独的 ConsoleBookPrinter 和 OtherMediaBookPrinter 类。
5.依赖注入 当有人提到“依赖注入”这个词时,可能会想到一些框架 - 谷歌的Guice,或者也许是Spring。但事实是,我们不需要复杂的框架来理解这个原则。 依赖注入只是在创建时注入类的依赖关系的技术,避免了危险的 新 关键字。 为了证明这一点,让我们使用代码实现Windows 98计算机:
public class Windows98Machine {} |
但没有显示器和键盘的电脑有什么用?让我们在构造函数中添加其中一个,以便我们实例化的每个 Windows98Computer 都预装了一个Monitor 和一个 键盘:
public class Windows98Machine { private final Keyboard keyboard; private final Monitor monitor; public Windows98Machine() { monitor = new Monitor(); keyboard = new Keyboard(); } } |
public class Windows98Machine{ private final Keyboard keyboard; private final Monitor monitor; public Windows98Machine(Keyboard keyboard, Monitor monitor) { this.keyboard = keyboard; this.monitor = monitor; } } |
优秀!我们已经解耦了依赖关系,并可以使用我们选择的任何测试框架自由测试我们的 Windows98Machine 。(banq注:可以使用builder模式实现两个输入参数的注入)
结论 在本教程中,我们深入探讨了面向对象设计的SOLID原则。 我们从一小段SOLID历史开始,以及这些原则存在的原因。 我们逐字逐字地用一个违反它的快速代码示例来分解每个原则的含义。然后,我们了解了如何修复代码 并使其符合SOLID原则。