面向对象设计的SOLID原则

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则的首字母缩写。这几条原则是非常基础而且重要的面向对象设计原则。正是由于这些原则的基础性,理解、融汇贯通这些原则需要不少的经验和知识的积累。

SRP : 单一责任原则

        当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。

        通常一个类被修改的几率很大,因此我们应该专注单一功能。假如我们把很多功能放在一个类中,功能之间就形成了关联,我们修改其中一个功能便有可能中止另一个功能,这时我们就需要测试来避免可能出现的问题,浪费了很多时间。一个好的设计是把不同的职责分离出来,放进不同的类中,这样改变任意一个不会影响到其他的应用程序。

OCP: 开放封闭原则

        软件实体应该是可扩展,而不可修改的。也就是说,对扩展开放,对修改关闭。这是另一种非常棒的设计原则,可以防止其他人更改已经测试好的代码。理论上,可以在不修改原有的模块的基础上,扩展功能。这也是开闭原则的宗旨。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。

        1、通过增加代码来扩展功能,而不是修改已经存在的代码。

        2、如果客户模块和服务模块遵循同一个接口来设计,那么客户模块可以不关心服务模块的类型,服务模块可以方便扩展服务(代码)。

        3、OCP支持替换的服务,而不用修改客户模块。

        例如:我们现在有两种发送信息的方式,现在我们要增加一中方式。我们将需要在原来的方法中新增一个方法,在修改调用它的地方,这并不符合OCP原则。我们可以抽象出一个发送的接口,里面有发送的方法,然后我们让之前的两种发送方法去实现它,这样一来我们不管增加几种发送发式,只需要添加它的实现类和实现接口就可以了。不用再修改已有的接口和实现类,很好的遵循了OCP原则。在项目中应用OCP原则可以限制代码的更改,一旦代码完成,测试和调试之后就很少再去更改。这减少了给现有代码引入新bug的风险,增强软件的灵活性。

LSP : 里氏替换原则

        当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。

          换句话来说就是子类型能够完全替换父类型,而不会让调用父类型的客户程序从行为上有任何改变。因此,所有的子类必须按照和他们父类相同方式操作。子类的特定功能可能不同,但是必须符合父类的预期行为。一般来说,如果父类型的一个子类型做了一些父类型的客户没有预期的事情,那这就违反LSP。

          LSP经典案例是矩形和正方形的案例,正方形是要求长等于宽,如果你应该使用矩形的时候你却使用了正方形,不可预期的错误会发生,因为正方形的尺寸是不能被修改 (修改了就不是正方形,违背事物内在逻辑一致性)。

         我们回到面向对象的基本概念上,子类继承父类表达的是一种IS-A关系,IS-A关系这种用法被认为是面向对象分析(OOA)基本技术之一。

       《Java与模式》一书中的解释是:我们设计继承体系时,子类应该是可替代的父类的,是可替代关系,而不仅仅是IS-A的关系。

       而PPP一书中的解释是:从行为方式的角度来看,正方形不是长方形,对象的行为方式才是软件真正所关注的问题 ,LSP清楚地指出,OOD(面向对象设计)中IS-A关系时就行为方式而言的,客户程序是可以对行为方式进行合理假设的。

       其实二者表达的是同一个意思。

ISP : 接口分离原则

        不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

        当你应用ISP时,类和他们的依赖使用紧密集中的接口通信,最大限度地减少了对未使用成员的依赖,并相应地降低耦合度。小接口更容易实现,提升了灵活性和重用的可能性。由于很少的类共享这些接口,为响应接口的变化而需要变化的类数量降低,增加了鲁棒性。

        不要依赖你不需要的东西:

        示例1:一个动物的行为接口中包含了“吃”、“爬”和“跑”,但蛇是不会跑的,所以有些是不必须的方法,更好的方法是我们将“爬”和“跑”单独做一个接口。这个要根据实际情况做调整,如果不是每个继承该接口的类都需要,就不能将所有的功能都放在同一个接口里。

        示例2:一个提款机有很多不同的操作,我们在取钱(拿走)的时候没服务费,在取钱(转账)的时候要扣服务费,我们能在取钱(拿走)的时候,提示取款扣服务费吗?       

        为什么要ISP(接口分离原则):

        否则 - 增加了不同客户端之间的耦合

        每个客户端对SRP基本上是一个变量

DIP: 依赖倒置原则

        1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象 
        2. 抽象不应该依赖于细节,细节应该依赖于抽象

        DIP是定位在高层次模块不应该依赖低层次模块,它们应该只依赖于抽象,这就能帮助我们实现松耦合,使得设计易于修改,DIP允许测试 数据库细节能够如插件一样插入我们的系统。

        1、高层模块不要依赖低层模块

        2、高层和低层模块都要依赖于抽象

        3、抽象不要依赖于具体实现

        4、具体实现要依赖于抽象

        5、抽象和接口使模块之间的依赖分离。

        例如我们经常会用到宏观的一种体系结构模式--layer模式,通过层的概念分解和架构系统,比如常见得三层架构等。那么依赖关系应该是自上而下,也就是上层模块依赖于下层模块,而下层模块不依赖于上层,如下图所示。
  
        这应该还是比较容易理解的,因为越底层的模块相对就越稳定,改动也相对越少,而越上层跟需求耦合度越高,改动也会越频繁,所以自上而下的依赖关系使上层发生变更时,不会影响到下层,降低变更带来的风险,保证系统的稳定。
       上面是立足在整体架构层的基础上的结果,再换个角度,从细节上再分析一下,这里我们暂时只关注UI和Service间的关系,如下面这样的依赖关系会有什么样的问题?
 
       第一,当需要追加提供一种新的Service时,我们不得不对UI层进行改动,增加了额外的工作。
       第二,这种改动可能会影响到UI,带来风险。
       第三,改动后,UI层和Logic层都必须重新再做Unit testing。

       那么具体怎么优化依赖关系才能让模块或层间的耦合更低呢?想想前面讲的OCP原则吧,观点是类似的。

       我们可以为Service追加一个抽象层,上层UI不依赖于Service的details,UI和Service同时依赖于这个Service的抽象层。如下图是我们的改进后的结果。
  
       这样改进后会有什么好处呢?
       第一,Service进行扩展时,一般情况下不会影响到UI层,UI不需要改动。
       第二,Service进行扩展时,UI层不需要再做Unit testing。
 

总结

       这几条原则是非常基础而且重要的面向对象设计原则。正是由于这些原则的基础性,理解、融汇贯通这些原则需要不少的经验和知识的积累。

      1、一个对象只承担一种责任,所有服务接口只通过它来执行这种任务。

      2、程序实体,比如类和对象,向扩展行为开放,向修改行为关闭。

      3、子类应该可以用来替代它所继承的类。

      4、一个类对另一个类的依赖应该限制在最小化的接口上。

      5、依赖抽象层(接口),而不是具体类。

本文部分来源:http://blog.csdn.net/dxpqxb/article/details/51732555

备注:

      层(layer)体系架构模式就是把应用系统分解成子任务组,其中每个子任务组处于一个特定的抽象层次上。

       IOC(控制反转):全称为:Inverse of Control。从字面上理解就是控制反转了,将对在自身对象中的一个内置对象的控制反转,反转后不再由自己本身的对象进行控制这个内置对象的创建,而是由第三方系统去控制这个内置对象的创建。

       DI(依赖注入):全称为Dependency Injection,意思自身对象中的内置对象是通过注入的方式进行创建。

      IOC就是一种软件设计思想,DI是这种软件设计思想的一个实现。而Spring中的核心机制就是DI。

原文地址:https://www.cnblogs.com/LoveSuk/p/6567239.html