我所到之处,一切都写得太难理解了。我决定自己写一个易于理解的关于 SOLID 原理的描述,并附上示例。同时,更详细地处理它们。
如果有人在阅读时有任何澄清/批评/改进建议,请写在评论或单独的答案中。好吧,或者,直接编辑我的答案。
我的问题/答案的主要目标正是为任何级别的人编写尽可能容易理解的内容:
- 用最简单和简短的例子。(总是打破一切来理解一堆别人的代码)
- 用最少的术语。最简单的语言。
很明显,有关该主题的文章是大海。但是易于理解并且代码列表很短。
就个人而言,我还没有找到任何东西。
我所到之处,一切都写得太难理解了。我决定自己写一个易于理解的关于 SOLID 原理的描述,并附上示例。同时,更详细地处理它们。
如果有人在阅读时有任何澄清/批评/改进建议,请写在评论或单独的答案中。好吧,或者,直接编辑我的答案。
我的问题/答案的主要目标正是为任何级别的人编写尽可能容易理解的内容:
很明显,有关该主题的文章是大海。但是易于理解并且代码列表很短。
就个人而言,我还没有找到任何东西。
SOLID是OOP原则。重要的是要理解这些是建议,而不是教条。不需要申请。您还需要了解,有时遵循其中一项原则可能会违反另一项原则。
应用这些原则的后果:
还有YAGNI的原则——你不需要它(你不需要它)。这个想法是,如果你现在没有客观的理由在这里应用一个原则或模式,那么你根本不需要应用它。以 SOLID 为例:如果您以后不需要修改和开发代码(例如 1 晚的项目)- 以更简单的方式编写代码,而不使用 SOLID。试图在开发的早期阶段创建一个灵活的解决方案通常是一个过于复杂的解决方案。
让我们回到我们的羊:
SOLID是以下的缩写:
让我们尝试使用 c# 语言的示例找出并理解它们。 我会出事的。
单一责任。“一个班级应该只有一个改变的理由。” 听起来很模糊,对吧?
PS:“单一职责/责任原则”听起来完全不直观,所以我建议你不要这样背。
更改类逻辑的原因:
这个原则的本质是类的功能必须是完整的,具有高度的逻辑连贯性。如果一个类有很多职责(许多“来自不同领域”的功能),那么它会经常变化。因此,如果一个类有多个职责,那么当代码更改时,这会导致设计脆弱并在意想不到的地方出现错误。任何复杂的类都必须分解成几个简单的组件,这些组件负责行为的某个方面。
让我们以伯德为例。有会飞的鸟,也有不会飞的鸟。如果我们创建一个会飞的鸵鸟或猕猴桃的实例,那会很奇怪。也就是说,而不是类
我们应该有 3 个类:
应用细微差别:
接口隔离是为每个客户端提供最小(理想情况下是独立的!)接口。
考虑代码:
代码问题:
根据ISP原理,我们需要将一个接口分成若干个:
IPrinter,IStaple,IFax,IScan,IPhotoCopy而且,由于我们有一台实现上述所有功能的通用机器,因此为它创建一个将从它们继承的接口。
并添加一个类构造函数
Machine那么这样一台万能机的创建过程如下:
通过这样做,我们可以获得以下好处:
打开/关闭- 程序实体必须对扩展开放,但对修改关闭。
这是什么意思?
Closed for modify意味着:你可以改变一个类\函数\模块的代码的唯一原因是直接改变其中嵌入的函数或者修复这个函数运行中的错误。不可能有其他原因。
开放扩展意味着:如果您需要您的类\功能\模块能够在新环境中执行其功能,他们必须在不更改代码的情况下支持它。
假设我们有一个类来对数组进行排序
此方法只能对整数进行排序。
如果我们的程序已经需要相同的方法,但不仅是整数(或者将来肯定会需要它)——这就是我们的扩展点。
我们使用 dotnet 中已有的接口,稍微改写一下代码:
我们通过这些变化取得了很多成就,即:
应用细微差别:
应用原理的第二个例子:通过继承。
我们有一个处理 CSV(逗号分隔值)的类。也就是说,它将表格写入文本文件,其中逗号是一行中的单元格分隔符,而新行是表格中的新行[通常,那里的一切都更复杂,但这是为了便于理解]
如果突然我们需要同一个类,但它也写列标题,那么我们只需要从这个类继承,而不是改变基类。
这篇文章是根据答案写的什么是开放和封闭的原则?
如果有兴趣,可以通过使用装饰器来应用此原则的第三个示例。
Liskov 替换——实现“CORRECT”多态的想法。也就是说,有必要以这样一种方式实现子类型继承,即可以替换它的任何子类型而不是基类型。
有时这很难做到。用一个例子来考虑实现它的复杂性:
假设我们有一些方法应该对鸭子做一些事情。无论。
public void DoSomethingWithDuck(IDuck: duck) {}.这里应用原理的难点在于,电鸭只有在开机的情况下才能游泳。也就是说,如果我们在这个方法中放入一个OFF电鸭子并调用
duck.Swim();它,它就不会游泳了。也就是说,在这种情况下,LSP 只是乍一看,但实际上并没有遵守这一原则。当然,这可以通过以下形式在此方法中使用拐杖来解决:
但这只是一根拐杖。
遵循原理的正确解决方案是更改电子鸭子,使其在调用该方法时自动打开
.Swim();。第二种方法是根本不让电鸭成为有机鸭的“亲戚”。
依赖转换。这个原则到处都被解释为“上层模块不应该依赖于下层模块。两者都应该依赖于抽象。” 对我来说,这句话很难理解。这在实践中意味着什么?
让我们转向该原则的作者(Bob Martin),他简短地说“你需要依赖抽象,而不是具体的东西。” 使用这个原理,一个模块可以很容易地被另一个模块替换,只需改变依赖模块,然后低级模块的任何变化都不会影响高级模块。
例如,它可能看起来像这样:
PasswordReminder 类依赖于 MySQLConnection。但是更高级别的 PasswordReminder 不应该依赖于更低级别的 MySQLConnection 模块。
如果我们需要将连接从 MySQLConnection 更改为 MongoDBConnection,那么我们必须将代码中编写的构造函数注入更改为 PasswordReminder 类。
PasswordReminder 类应该依赖于抽象,而不是任何特定的东西。但是怎么做呢?
由于这些变化
PasswordReminder()只依赖于抽象。我们可以通过改变一种方法来连接 MongoDB 而不是Connect()MYSQL——或者创建一个 MongoDBConnection 并从 IDbConnection 继承;