鲍勃大爷:SOLID概念解释


SOLID原则与90年代(甚至更早于20世纪90年代)一样,在今天同样重要。这是因为软件在这些年中并没有发生太大变化:因为自1945年Turing编写电子计算机的第一行代码以来,软件并没有发生太大变化。软件仍然是if语句,while循环和赋值语句、Sequence,Selection和Iteration。
每次新一代都喜欢认为自己的世界与前一代大不相同。这是错误的。
因此,让我们一步一步地讲解这些原则。以下是针对Dan North在SOLID上的观点:为什么SOLID的每个元素都不正确?只需编写简单的代码即可。”
 
SRP)单一责任原则。
将因相同原因而发生变化的事物聚集在一起。将因不同原因而改变的事物分开。
很难想象这个原理与软件无关。我们不会将业务规则与GUI代码混合在一起。我们不会将SQL查询与通信协议混合使用。我们将因不同原因而更改的代码分开存放,以便对一个部分进行更改而不会破坏其他部分。我们确保因不同原因而更改的模块不具有使它们纠缠的依赖性。
微服务无法解决此问题。如果您混合了由于不同原因而更改的代码,则可以创建一个复杂的微服务或一个复杂的微服务集。
丹·诺斯(Dan North)的幻灯片完全错过了这一点,并让我确信他根本不了解该原理。他对SRP的回答是“写简单代码”。我同意。SRP是我们使代码保持简单的方法之一。
 
OCP)开放-封闭原则。
一个模块应该可以扩展,但是可以关闭以进行修改。
在所有原则中,任何人都会对此提出质疑的想法使我对我们行业的未来充满了恐惧。当然,我们希望创建无需扩展即可扩展的模块。您能想象在没有设备独立性的系统中工作吗?在该系统中,写入磁盘文件与写入打印机,屏幕或管道根本不同?我们是否希望看到if语句分散在我们的代码中以处理所有小细节?
还是……我们要将抽象概念与详细概念分开吗?我们是否要使业务规则与GUI的烦琐细节,微服务通信协议以及数据库的任意行为隔离开来?当然可以!
同样,Dan的幻灯片完全错了。当需求更改时,仅现有代码的一部分是错误的。现有的许多代码仍然正确。而且,我们希望确保不必为了使错误的代码再次起作用而更改正确的代码。丹的答案是“编写简单的代码”。我再次同意。而且,具有讽刺意味的是,他是对的。 简单的代码既是打开的又是关闭的。
 
LSP)Liskov替代原则。
使用接口的程序一定不要与该接口的实现混淆。
人们(包括我)都犯了一个错误,那就是继承。它不是。它是关于子类型的。接口的所有实现都是接口的子类型。所有Duck类型都是隐式接口的子类型。并且,基本接口的每个用户,无论是声明的还是隐含的,都必须同意该接口的含义。如果实现使基本类型的用户感到困惑,则if/switch语句将激增。
这个原则是关于使抽象保持清晰和定义良好。不可能相信这是一个过时的概念。
丹的幻灯片在该主题上是完全正确的。他只是错过了原则的要点。简单代码是保持清晰的子类型关系的代码。
 
ISP)接口隔离原理。
保持接口较小,以使用户最终不必依赖于他们不需要的东西。
我们仍然使用编译语言。我们仍然依靠修改日期来确定应重新编译和重新部署哪些模块。只要这是真的,我们就必须面对这样一个问题:当模块A在编译时依赖于模块B ,而在运行时不依赖于模块B时,对模块B的更改将迫使模块A重新编译和重新部署。
在静态类型的语言(例如Java,C#,C ++,GO,Swift等)中,此问题尤其严重。动态类型的语言受到的影响要小得多。但仍然不能幸免。Maven和Leiningen的存在就是证明。
丹在这个主题上的幻灯片被证明是错误的。客户端确实依赖于他们不调用的方法,如果在修改其中一种方法时必须重新编译和重新部署它们的话。就此原则而言,Dan关于此原则的最终观点是可以的。是的,如果您可以将具有两个接口的类拆分为两个单独的类,那么这样做(SRP)是个好主意。但是这种分离通常是不可行的,甚至是不希望的。
 
DIP)依赖倒置原则。
取决于抽象的方向。高级模块不应依赖于低级细节。
很难想象没有大量使用此原理的体系结构。我们不希望我们的高级业务规则取决于低级详细信息。我希望这是显而易见的。我们不希望那些为我们赚钱的计算受到SQL或低级验证或格式问题的污染。我们希望将高级抽象与低级细节隔离开来。这种分离是通过仔细管理系统中的依赖关系来实现的,以便所有源代码依赖关系(尤其是那些跨越体系结构边界的源代码)都指向高级抽象而不是低级细节。
在以上每种情况下,Dan的幻灯片都以结尾:只编写简单的代码。这是个好建议。但是,如果这些年来教会了我们什么,那么简单需要有简单的原则。正是这些原则约束了程序员生成倾向于简单的代码。
使一个复杂的混乱变得最好的方法是告诉每个人“简单”,不要给他们进一步的指导。