OOP 的 SOLID 原则
如果你学到了新东西,欢迎随时在LinkedIn上联系我,或者在 dev.to 上关注我 :)
我觉得这个话题现在很少有人讨论了,所以以下是我对此的解释。SOLID
是 5 项核心原则的首字母缩写。这些原则旨在让你的代码在使用面向对象语言编写时更易于维护、更健壮。它们分别代表:
- 单一职责
- 打开/关闭
- 里氏替换
- 接口隔离
- 依赖倒置(注入)
振作起来!还有很多事情要做……
单一职责:
本质上,你的方法/类应该只做一件事,并且要做好。例如,以下代码很糟糕:
这很糟糕,因为在名为“Count”的方法中向控制台写入任意文本毫无意义。这是意料之外且不必要的行为。另一方面,这很好。这很好,因为现在每个方法都有了特定的功能。
让一个对象只做一件事,可以让代码更易于阅读和理解。而且,如果需要扩展,也可以更快地完成。
开/关:
简而言之,软件实体(方法、类等)应该对扩展开放,对修改关闭。嗯……我想是时候再举一个例子了!在下面的代码中,我们试图赋予方法太多功能。这样做,我们就修改了方法的预期行为。正如你所见,修改这样的内容可能会非常麻烦。
现在考虑一下。我们拆分类,添加一个接口,并让每个对象指定如何处理订单。现在,我们有了一个更易于扩展的东西。
如您所见,通过添加一层继承,我们并没有改变接口的底层行为,但却将其开放以供扩展。现在,要遵循上面的例子,您需要在调用类中为两个对象添加一个 if 语句,并在它们之间进行切换。但您明白了,通过抽象出功能,我们使代码更加灵活,包括派生类能够以任何它们想要的方式实现接口中的函数。
利斯科夫替换:
简单来说,这个原则的意思是,一个软件实体应该可以被替换成子类型,而不会产生任何新的错误。这里的子类型指的是继承自其他类型的实体。例如,Order 继承自 IOrder。我感觉还有另一个例子:
在上面的示例中,我们违反了规则,因为我们应该能够像使用 Order 对象一样使用 BigOrder 对象,但目前我们做不到。这两个对象都是独立对象的具体实现,也就是说,它们没有共同的子类型。这在现实世界中可能会有问题,尤其是在测试软件组件时。实现相同功能的更好方法是,创建一个新的方法来接收 BigOrder 对象(这样做很糟糕,不要这样做),或者改为使用接口。
在上面的示例中,您可以清楚地看到 IOrder 的两个实现(BigOrder 和 Order 都继承自 IOrder)可以相互替换,而不会影响程序的功能。
接口隔离:
这意味着,如果不需要,就不要在接口中添加新方法。例如,以下做法很糟糕:
现在显然汽车不应该有任何飞行功能,至少在2021年,或者2030年……同样,飞机也不应该能够飞行。因此,我们为这两个对象引入了冗余功能。这违反了这一原则。我们可以通过引入新的接口来更好地管理新功能,从而解决这个问题。
当你看到它的实际效果时就会明白。
依赖倒置(注入):
最后,伟大的DI(依赖倒置(注入))。这本身就是一个庞大的话题。实现它的方法有很多。我喜欢的想法是,不要在应用程序的组成路径中使用“new”关键字。这条原则指出:
- 高级/低级模块应该依赖于抽象
- 抽象不应该依赖于细节
本质上,不要在代码中依赖具体的实现。具体的实现是使用“new”关键字创建的实例或对象。如果您已经读到这里,首先,做得很好;其次,您可能已经看到一些示例中使用了依赖注入 (DI)。从下面的示例中,您可以看到这条规则被违反了,因为在 BuildHouse 方法中,我们使用 new 关键字创建了 BrickLayer 对象的实例。这种额外的依赖关系降低了我们方法的可测试性。
现在我们首先要做的是将 BrickLayer 注入到 BuildHouse 方法中。不过,我们还可以更进一步,将 BrickLayer 抽象出来。
太棒了,我们成功了!现在来看看证明。
太棒了。现在,如果我们需要在方法中插入一个虚拟的 IConstructionWorker 对象来测试它,我们可以做到,而且没有任何问题。
希望我的讲解能帮助你理解 SOLID 原则。下次再见。