OOP 的 SOLID 原则

2025-05-24

OOP 的 SOLID 原则

如果你学到了新东西,欢迎随时在LinkedIn上联系我,或者在 dev.to 上关注我 :)


我觉得这个话题现在很少有人讨论了,所以以下是我对此的解释。SOLID
是 5 项核心原则的首字母缩写。这些原则旨在让你的代码在使用面向对象语言编写时更易于维护、更健壮。它们分别代表:

  • 单一职责
  • 打开/关闭
  • 里氏替换
  • 接口隔离
  • 依赖倒置(注入)

振作起来!还有很多事情要做……



单一职责:

本质上,你的方法/类应该只做一件事,并且要做好。例如,以下代码很糟糕:

图像

这很糟糕,因为在名为“Count”的方法中向控制台写入任意文本毫无意义。这是意料之外且不必要的行为。另一方面,这很好。这很好,因为现在每个方法都有了特定的功能。

图像

让一个对象只做一件事,可以让代码更易于阅读和理解。而且,如果需要扩展,也可以更快地完成。



开/关:

简而言之,软件实体(方法、类等)应该对扩展开放,对修改关闭。嗯……我想是时候再举一个例子了!在下面的代码中,我们试图赋予方法太多功能。这样做,我们就修改了方法的预期行为。正如你所见,修改这样的内容可能会非常麻烦。

图像

现在考虑一下。我们拆分类,添加一个接口,并让每个对象指定如何处理订单。现在,我们有了一个更易于扩展的东西。

图像

如您所见,通过添加一层继承,我们并没有改变接口的底层行为,但却将其开放以供扩展。现在,要遵循上面的例子,您需要在调用类中为两个对象添加一个 if 语句,并在它们之间进行切换。但您明白了,通过抽象出功能,我们使代码更加灵活,包括派生类能够以任何它们想要的方式实现接口中的函数。



利斯科夫替换:

简单来说,这个原则的意思是,一个软件实体应该可以被替换成子类型,而不会产生任何新的错误。这里的子类型指的是继承自其他类型的实体。例如,Order 继承自 IOrder。我感觉还有另一个例子:

图像

在上面的示例中,我们违反了规则,因为我们应该能够像使用 Order 对象一样使用 BigOrder 对象,但目前我们做不到。这两个对象都是独立对象的具体实现,也就是说,它们没有共同的子类型。这在现实世界中可能会有问题,尤其是在测试软件组件时。实现相同功能的更好方法是,创建一个新的方法来接收 BigOrder 对象(这样做很糟糕,不要这样做),或者改为使用接口。

图像

在上面的示例中,您可以清楚地看到 IOrder 的两个实现(BigOrder 和 Order 都继承自 IOrder)可以相互替换,而不会影响程序的功能。



接口隔离:

这意味着,如果不需要,就不要在接口中添加新方法。例如,以下做法很糟糕:

图像

现在显然汽车不应该有任何飞行功能,至少在2021年,或者2030年……同样,飞机也不应该能够飞行。因此,我们为这两个对象引入了冗余功能。这违反了这一原则。我们可以通过引入新的接口来更好地管理新功能,从而解决这个问题。

图像

当你看到它的实际效果时就会明白。



依赖倒置(注入):

最后,伟大的DI(依赖倒置(注入))。这本身就是一个庞大的话题。实现它的方法有很多。我喜欢的想法是,不要在应用程序的组成路径中使用“new”关键字。这条原则指出:

  1. 高级/低级模块应该依赖于抽象
  2. 抽象不应该依赖于细节

本质上,不要在代码中依赖具体的实现。具体的实现是使用“new”关键字创建的实例或对象。如果您已经读到这里,首先,做得很好;其次,您可能已经看到一些示例中使用了依赖注入 (DI)。从下面的示例中,您可以看到这条规则被违反了,因为在 BuildHouse 方法中,我们使用 new 关键字创建了 BrickLayer 对象的实例。这种额外的依赖关系降低了我们方法的可测试性。

图像

现在我们首先要做的是将 BrickLayer 注入到 BuildHouse 方法中。不过,我们还可以更进一步,将 BrickLayer 抽象出来。

图像

太棒了,我们成功了!现在来看看证明。

图像

太棒了。现在,如果我们需要在方法中插入一个虚拟的 IConstructionWorker 对象来测试它,我们可以做到,而且没有任何问题。


希望我的讲解能帮助你理解 SOLID 原则。下次再见。

文章来源:https://dev.to/albertbennett/solid-principals-for-oop-2e49
PREV
程序员必备的 5 款最佳免费笔记应用
NEXT
在 React 中构建实时搜索过滤器:分步指南