上篇文章说了工厂模式的单例模式和创建模式,单例模式如何在懒加载的情况下保证线程安全性,创建模式通过接口和抽象类,来完成开闭原则。


单一职责原则,开闭原则,里氏替换原则,依赖导致原则,接口隔离原则,迪米特原则。

  • 单一职责原则(single responsibility principle)SRP:

定义:一个类只负责一个功能领域中相应的职责,或者定义为,就一个类而言,应该只有一个引起他变化的原因。

问题原因是类A有两个不同职责方法,当修改其中一个的时候导致其他方法出现问题。

单一职责就是实现 高内聚、低耦合 的知道方针,当一个类里承担的责任越多,他被复用的可能性就越小,因此要将一些职责分离,将不同的职责封装在方法或者类里。发现类的职责需要设计人员较强的分析能力和相关经验。

需不需要拆分,取决于是否会发生变化,如果修改其中一个职责,不影响其他职责,则不需要拆分,否则则需要拆分。

所以SRP单一职责不光使用与接口和类,也适用于方法。

优点:

  1. 降低类的复杂度,逻辑比负责多项职责简单。
  2. 提高类的高复用性。
  3. 风险降低,修改一个职责的时候,影响范围更小。

二、开闭原则(open-closed principle,OCP)

定义:一个软件实体应当对扩展开放,对修改关闭。在不修改的情况下可以扩展。

问题原因是因为任何一个需求不可能一成不变,随着时间的推进,业务会增加,这时候再增加新的需求时候,不能影响以前的需求,导致以前的需求不可用。

为了满足开闭原则,需要对系统进行抽象化设计,这是开闭原则的关键。当需要扩展的时候,只需要实现这个抽象类即可,无需对抽象类进行任何改动,实现不修改已有代码的基础进行扩展。

当代码有很多ifelse的时候,可以建立一个抽象的基类,然后如果有新的可以增加,则只需要增加集成的子类即可,不需要改变原有的代码。

优点:

  1. 开闭原则非常有名,只要是面向对象编程,都会有开闭原则。
  2. 当如同上面的抽象设计之后,开闭原则大大提高了复用性,在面向对象编程中,所有逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现的一套业务逻辑。逻辑的颗粒度越小,则越可以考虑复用。
  3. 提高系统的维护性和扩展性,当维护人员去维护系统的时候,更多的是希望扩展一个类,而不是修改原有的代码。
  4. 万物皆对象,我们把所有的事物抽象成对象,然后针对对象操作。设置之初尽可能考虑到所有场景,这需要开发人员的全局掌握和工作经验。

如何使用开闭原则呢?

通过抽象来约束,

  1. 通过接口或者抽象类来约束,对扩展进行边界限定,不允许出现抽象类不存在的public方法。
  2. 参数引用对象尽量使用接口或者抽象类,而不是实现类,这主要是里氏替换原则,后面会详细介绍。
  3. 抽象类必须保证稳定,确定后就不要修改。

三、里氏替换原则(Liskov Substutution Principle,LSP)

定义:所有引用基类的地方必须透明的使用其子类对象。

继承优点:减少代码工作量,提高代码重用性,子类可以形似父类,但异于父类,提高可扩展性。

继承缺点:继承有侵入性,当父类代码修改过后,必须要考虑是否影响到其他子类的职责是否会异常,缺乏代码规范的情况下,这时候会造成大量的重构改动。

如何克服上面的缺陷,于是里氏替换原则。

里氏替换原则告诉我们,软件中将一个基类替换成它的子类对象,不会有任何异常,但如果把子类替换成基类,则不成立的,因为子类有很多属于自己的其他东西。

里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因为在程序中可以用基类类型来定义,而在运行的时候在确定其子类类型,用子类替换父类对象。

使用需要注意几点:

  1. 子类所有方法必须在父类中声明,或者子类必须实现父类中声明的所有方法。为了保证程序扩展性,在程序中通常用父类来定义,如果一个方法只存在子类,则肯定不可以用父类来调用。
  2. 我们在运用时候,尽量把基类设置成接口或者抽象类,当需要扩展的时候,只需要新增集成的子类,不需要修改原有的代码。
  • 依赖倒置原则(Dependence inversion principle,DIP)

定义:需要面向接口编程,而不是面向实现,高层模块和底层模块不应该依赖,而是依赖抽象,抽象不应该依赖细节,细节依赖抽象。

依赖倒置原则是我们在程序代码传递参数关联时,尽量引用高层次的抽象类,为了确保这一原则,所以具体类应该只实现接口或者抽象类存在的方法,否则都发通过接口来调用子类新增的方法。

引用接口和抽象类,系统更具有灵活性,这样一来,系统发生变化,在抽象类或者接口进行扩展。

实现依赖倒置我们需要DI依赖注入的方式,常用的有构造函数注入和接口注入。

优点:

  1. 降低系统耦合度。
  2. 减少并行的风险。
  3. 提高代码可读性和维护性。

用的时候,子类的所有方法必须在父类中有,这也就是必须遵循里氏替换原则,尽量把父类设计成接口或者抽象类,或者都有。

  • 接口隔离原则(interface segregation principle ISP)

定义:使用多个专门的接口,不使用单一总接口,客户端不应该依赖那些不需要的接口。

当一个接口太大的时候,我们需要将它分割成一些更细小的接口,客户端仅仅需要知道相关的方法即可。每个接口承担独立的角色。这里的接口有两层定义,一种是类具有的方法和特征,逻辑上的接口隔离。一种是某种语言定义的接口,如java 的interface接口。

如果是第一种,则ISP代表着角色隔离接口,根据不同的业务逻辑来区分。

如果是第二种,理解成语言的接口,那么ISPD代表为客户端提供的行为。在面向对象语言中,实现一个接口就要实现该接口所有方法,所以应该尽可能小的提供接口。

优点:

  1. 将臃肿的接口拆分为小的,提高可复用性和可维护性。
  2. 降低了接口系统的耦合性。
  3. 如果接口的颗粒度合理,则可以保证系统的稳定性,如果定义太小会造成接口数多。
  4. 减少代码冗余量。
  • 迪米特法则(law of demeter lod)

定义:一个软件实体尽可能少的与其他实体发生相互作用。

该定义是由一个迪米特的研究项目来命名的,一个对象只能与直接朋友发生交互,不要与陌生人直接发生交互,这样可以做到降低系统耦合度,改变则不会影响其他对象。

比如项目中每个功能尽可能的不要相互依赖,而是去使用某个功能的时候,才去调用用到它。

优点:

  1. 降低类之间的耦合度,提高独立性。
  2. 进而又提高了复用性和扩展性。

但是在使用的时候会出现很多中介类,从而使系统更加复杂,所以需要反复衡量。

单一职责让我们的类各司其职,职责单一,里氏替换告诉我们优化继承体系,依赖倒置是面向接口编程,通过构造函数等其它方式注入,接口隔离告诉我们设计接口要单一,迪米特告诉我们要解耦,最后达到我们的开闭原则,遵循扩展开发,修改关闭。