设计模式六大原则

发布于 3649 字 8 分钟 0 设计模式 Java设计模式

遵循设计原则是为了建立稳定、灵活、健壮的设计,而开闭原则又是重中之重,是最基础的原则,是其他五大原则的精神领袖

单一职责(Single Responsibility Principle, SRP)

What

应该有且仅有一个原因引起类的变更,即一个接口或类只有一个原因引起变化,一个接口或类只有一个职责,它就负责一件事情

Why

  • 类的复杂性降低,实现什么职责都有明确的定义
  • 可读性提高
  • 可维护性提高
  • 变更引起的风险降低. 变更是必不可少的,如果接口的单一职责做的好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,对系统的拓展性、维护性都有非常大的帮助

How

接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化

注意

单一职责原则提出一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良, 但是“职责”或“变化原因”都是不可度量的,所以因项目而异,因环境而异.适用于接口、类,同时也适用于方法,接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化,一个方法尽可能做一件事情.

里氏代换原则(Liskov Substitution Principle, LSP)

What

  • 第一种定义,也是最正宗的定义: 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S就是类型T的子类型.
  • 第二种定义:所有引用基类的地方必须能透明地使用其子类的对象

通俗的说,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本不需要知道是父类还是子类,但是,有子类的出现的地方,父类未必能适应.

Why

减少继承的弊端,发挥继承的最大优势

How

  • 子类必须完全实现父类的方法
  • 子类可以有自己的个性
  • 覆盖或实现父类的方法时,输入参数可以放大子类中实现的方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松
  • 覆写或实现父类的方法时输出的结果可以被缩小

Other

采用里氏替换原则时,尽量避免子类的"个性”,一旦子类有"个性”,这个子类和父类之间的关系就很难调和. 把子类当做父类使用,子类的"个性"被抹杀,把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离,缺乏类替换的标准. 在类中调用其他类时务必使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则; 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生"畸变”,则建议断开父子继承关系,采用依赖,聚集,组合等关系代替继承.

关于继承

依赖倒转原则(Dependence Inversion Principle, DIP)

What

广义上

  • 高层模块不应该依赖底层模块,两者都应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

在Java中

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系时用过接口或抽象类产生的.
  • 接口或抽象类不依赖于实现类
  • 实现类依赖接口或抽象类

其核心就是面向接口编程—OOD(面向对象设计)的精髓之一

倒置含义 正置就是类间的依赖时实实在在的实现类间的依赖,也就是面向实现编程,这也是人们传统思维中的事物之间的依赖. 对现实世界的事物进行抽象,抽象的结果就是有了抽象类和接口,然后根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物之间的依赖,“倒置"就是从这里产生的

Why

  • 减少类间的耦合性
  • 提高系统的稳定性
  • 降低并行开发引起的风险
  • 提高代码的可读性和可维护性
  • 是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展的开放,对修改的关闭.

How

  • 构造函数传递依赖对象
  • setter方法传递依赖对象
  • 接口声明依赖对象(接口注入)

规则

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

接口隔离原则(Interface Segregation Principle)

What

  • 实例接口, Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型事物的描述,这是一个接口.
  • 类接口, Java中interface关键字定义的接口

Why

  • 客户端不应该依赖它不需要的接口
  • 类之间的依赖关系应该建立在最小的接口上
  • 隔离总结 建立单一接口,不要建立臃肿庞大的接口,通俗讲,接口尽量细化,同时接口中的方法尽量少

How

  • 接口尽量小, 根据接口隔离原则拆分接口时,首先必须满足单一职责原则
  • 接口要高内聚
  • 定制服务, 只提供访问者需要的方法
  • 接口设计有限度的, 接口设计注意适度

和单一原则区别

审视角度不相同,单一职责要求的时类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少

Other

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装,如何划分原子?

  • 一个接口只服务于一个子模块或业务逻辑
  • 通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口精简单一,而不是臃肿庞大
  • 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理
  • 了解环境,拒绝盲从,每个项目或产品都有特定的环境因素,环境不同,接口拆分的标准就不同,需要深入了解业务逻辑

迪米特法则/最少知道原则 (Law Of Demeter/Least Knowledge Principle, LOD)

What

一个对象应该对其他对象有最少的了解. 通俗的说,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没有关系,我只需要知道你提供哪些public方法,我就调用这些,其他的一概不关心

Why

类间解耦,弱耦合 (但是解耦时有限度的),提高类的复用率.但是其要求结果会产生大量的中转或跳转类,倒置系统的复杂性提高,同时也为维护带来了难度,使用迪米特法则时需要反复权衡,既做到让结构清晰,又要做到高内聚低耦合.

How

  • 只和朋友交流。一个类只和朋友交流,不与陌生类交流,类与类之间的关系时建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象
  • 朋友也是有距离的。尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限
  • 是自己的就是自己的。如果一个方法放在本类中,即不增加类间关系,也不对本类产生负面影响,就放置在本类中
  • 谨慎使用Serializable

开闭原则(Open-Close Principle)

What

一个软件实体如类、模块和函数对扩展开发,对修改关闭

软件实体包括以下几个部分:

  • 项目或软件产品中按照一定的逻辑规则划分的模块
  • 抽象和类
  • 方法

注意 开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段

变化归纳

  • 逻辑变化. 只变化一个逻辑,而不涉及其他模块,可以通过修改原有类中的方法的方式来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理.
  • 子模块变化. 一个模块变化,会对其他模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化.
  • 可见视图变化. 提供给客户使用的界面,该部分的变化一般会引起连锁反应

Why

  • 有利于测试,只需要保证提供的方法的正确性(孤立测试)
  • 可以提高复用性
  • 可以提高维护性
  • 面向对象开发的需求

建立稳定灵活、健壮的设计,而开闭原则是重中之重,是最基础的原则,是其他5大原则的精神领袖

How

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

通过接口或抽象类约束扩展,多扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法 参数类型、引用对象尽量使用接口或抽象类,而不是实现类. 抽象层尽量保持稳定,一旦确定即不允许修改

  • 尽量使用元数据控制模块行为

元数据是指用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得. 如达到极致的就是控制反转,使用做多的就是Spring容器

  • 定制项目章程, 因为章程中指定了所有人员都必须遵守的约定,对于项目来说,约定优于配置.
  • 封装变化

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

注意

  • 开闭原则也只是一个原则

开闭原则只是精神口号,实现拥抱变化的方法非常多,并不局限于这六大设计原则,但是遵循这六大设计原则基本上可以应对大多数变化.项目中应尽量采用这六大原则,适当时候可以进行扩充

  • 项目规章非常重要

优秀的章程能给项目带来非常多的好处,如提高开发效率、降低缺陷率、提高团队士气、提高技术成员水平等等

  • 预知变化。

一旦发现有变化的可能,或者变化曾经发生过,则需要考虑现有的架构是否可以轻松地实现这一变化.设计一套优良的架构不仅要符合现有的需求,还需要适应可能发生的变化.

其他原则

  • 面向接口编程
  • 优先使用组合,而非继承
  • 一个类需要的数据应该隐藏在类的内部
  • 类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没有关系,要么只使用另一个类的接口提供的操作
  • 在水平方向上尽量可能统一地分布系统功能 等等