SOLID基本原则

6大设计基本原则:单一职责原则里氏替换原则依赖倒置原则接口隔离原则迪米特法则开闭原则

单一职责原则SRP

​ 单一职责原则提出了一个编写程序的标准,用职责变化原因来衡量接口或类设计得是否优良,但职责和变化原因都是不可度量得,因项目和环境而异。单一职责适用于接口、类、方法

定义:应该有且仅有一个原因引起类的变更

优点类复杂性降低,实现职责清晰明确可读性高可维护性高变更引起的风险低

里氏替换原则LSP

定义每一个类型为S的对象s,都有类型为T的对象t,使得以T定义的所有程序P在所有的对象s都代替成t时,程序P的行为无变化,则类型S是类型T的子类;所有引用基类的地方必须能透明的使用其子类的对象。

  • 子类必须完全实现父类的方法:若子类不能完全实现父类方法,或某些方法在子类种已发生畸变,建议断开父子继承关系。
  • 子类可以有自己的个性:子类出现的地方父类未必能出现
  • 覆盖或实现父类方法时输入参数可以被放大
  • 覆写或实现父类的方法时输出结果可以被缩小

在类中调用其他类时务必使用父类或接口,否则即是违背LSP原则。

依赖倒置原则DIP

​ 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。可以通过依赖倒置原则涉及的接口或抽象类对实现类进行约束,可减少需求变化引起的工作量剧增的情况,可让维护人员轻松地扩展和维护,是实现开闭原则的重要途径。TDD测试驱动开发模式就是依赖倒置原则的最高级应用。

定义高层模块不应该依赖底层模块两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象

表现:模块间依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生;接口和抽象类不依赖于实现类;实现类依赖接口或抽象类;

依赖的三种写法:

  • 构造函数传递依赖对象,也叫构造函数注入
  • Setter方法传递依赖对象,也叫Setter依赖注入
  • 接口声明依赖对象,也叫接口注入

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。

  • 每个类尽量都有接口或抽象类,或抽象类和接口两者都具备
  • 变量的表面类型尽量是接口或抽象类
  • 任何类都不应该从具体类派生
  • 尽量不要覆写基类的方法
  • 结合里氏替换原则使用

接口隔离原则ISP

定义:客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。建立单一的接口,不要建立臃肿庞大的接口

  • 接口尽量小
  • 接口要高内聚:提高接口、类、模块的处理能力,减少对外的交互
  • 定制服务,单独为一个个体提供优良的服务
  • 接口设计要有限度

根据接口隔离原则拆分接口时,首先必须满足单一职责原则。接口和类尽量使用原子接口或原子类来组装。

  • 一个接口只服务玉一个子模块或业务
  • 通过业务逻辑压缩接口中的public方法
  • 已经被污染的接口,尽量去修改,若变更风险较大,则采用适配器模式进行转化处理
  • 了解环境,拒绝盲从

迪米特法则LD

也称最少知识原则:一个对象应该对其他对象有最少的了解,对需要耦合或调用的类知道越少越好

  • 只和朋友交流:类与类间的关系是建立在类之间而不是方法间,一个方法尽量不引入一个类中不存在的对象
  • 朋友间也是有距离的:尽量不对外公布太多public方法和非静态得public变量,尽量内敛
  • 是自己的就是自己的:若一个方法放在本类中,即不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
  • 谨慎使用Serializable

两个对象之间的耦合就成为朋友关系,朋友关系类型很多如组合、聚合、依赖等。

:朋友类的定义,出现在成员变量方法输入输出参数中的类称为成员朋友类,出现在方法体内部的类不属于朋友类

迪米尔法则要求类羞涩一点,尽量不对外公布太多public方法和非静态得public变量,尽量内敛,多使用privatepackage-privateprotected等访问权限。类公开的public属性或方法越多,修改时涉及得面也就越大,变更引起得风险扩散也就越大。迪米特法则核心观念是类间解耦、弱耦合。

缺点:会产生大量中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。使用时请反复权衡,既做到让结构清晰,又做到高内聚底耦合。

开闭原则OCP

一个软件实体如类、模块和函数应该对扩展开放对修改关闭。应该通过扩展来实现变化而不是通过修改已有代码来实现变化

开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有更高层模块进行耦合。

可把变化大致分为三类:

  • 逻辑变化:可以通过修改原有类中的方法来完成,前提是所有依赖或者关联类都按照相同的逻辑处理。
  • 子模块变化:底层模块变化必然引起高层模块变化,因此通过扩展完成变化时,高层模块修改是必然的
  • 可见视图变化

开闭原则是最基础的一个原则,前五个原则都是开闭原则的具体形态,前五个原则就是指导设计的工具和方法,而开闭原则才是精神领袖

开闭原则对测试的影响

在比较重要的方法,测试方法都会很多,可能测试逻辑都很复杂,若要通过修改修改一个方法或多个方法来完成变化,基本上测试用例都得重新写。所以需要通过扩展来实现业务逻辑而不是修改。

开闭原则可以提高复用性

在面向对象设计中,所有的逻辑都是从原子逻辑组合而来,而不是在一个类中独立实现一个业务逻辑。颗粒度越小,被复用的可能性就越大。避免相同的逻辑分撒在多个角落,缩小颗粒度,直到一个逻辑不可再拆分为止。

开闭原则可以提高可维护性
面向对象开发的要求

开闭原则应用

抽象约束

通过接口抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放

  • 通过接口或抽象类约束扩展,对扩展边界限定,不允许出现在接口或抽象类中不存在的public方法
  • 参数类型、引用对象尽量使用接口或抽象类,而不是实现类
  • 抽象层尽量保持稳定
元数据(metadata)控制模块行为

尽量使用元数据来控制程序行为,减少重复开发,如login方法中提供的先检查IP地址是否在允许访问的列表中,然后在确定是否需要到数据库中验证密码,表达的极致其实就是控制反转,如SpringIoC容器。

注:元数据是用来描述环境和数据的数据,通俗的说就是配置参数。

制定项目章程

对于项目来说约定优于配置。

封装变化

对变化的封装:将相同的变化封装到一个接口或抽象类中,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。

封装变化,也就是受保护的变化,找出预计有变化或不稳定的点。