Java设计模式-六大原则

一、单一职能原则(Single Responsibility Principle, SRP)

定义

There should never be more than one reason for a class to change.

应该有且仅有一个缘由引发类的变动数据库

换言之,也就是一个接口或类只有一个职责编程

好处

  • 类的复杂性下降,实现什么职责都有清晰明确的定义;
  • 可读性提升,复杂性下降,那固然可读性提升了;
  • 可维护性提升,可读性提升,那固然更容易维护了;
  • 变动引发的风险下降,变动时必不可少的,若是接口的单一职责作得好,一个接口修改只对相应的实现类有影响,对其余的接口无影响,这对系统的扩展性、维护性都有很是大的帮助。

最佳实践

职责的划分很难,要想彻底符合单一职责的设计更难,原则是接口必定要作到单一职责,类的设计尽可能作到只有一个缘由引发变化设计模式

二、里氏替换原则(LiskovSubstitution Principle,LSP)

继承的利与弊

  • 优势架构

    • 代码共享,减小建立类的工做量,每一个子类都拥有父类的方法和属性;
    • 提升代码的重用性;
    • 子类能够形似父类,但又异于父类;
    • 提升代码的可扩展性,实现父类的方法就能够“随心所欲”了;
    • 提升产品或项目的开放性。
  • 缺点函数

    • 继承是侵入性的。只要继承,就必须拥有父类的全部属性和方法;
    • 下降代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
    • 加强了耦合性。当父类的常量、变量和方法被修改时,须要考虑子类的修改,并且在缺少规范的环境下,这种修改可能带来很是糟糕的结果——大段的代码须要重构。

定义

If for each object o1 of type S there is an object o2 oftype T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 issubstituted for o2 then S is a subtype of T.

若是对每个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的全部程序P在全部的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。工具

以上是LSP"最正宗"的定义,可是可能不太容易理解,它还有另一种相对清晰明确的定义:性能

Functions that use pointers or references to base classes must be able to useobjects of derived classes without knowing it.

全部引用基类的地方必须能透明地使用其子类的对象。设计

通俗点讲,只要父类能出现的地方子类就能够出现,并且替换为子类也不会产生任何错误或异常,使用者可能根本就不须要知道是父类仍是子类。可是,反过来就不行了,有子类出现的地方,父类未必就能适应。(LSP能够正着用但不能反着用)orm

规范

里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义:对象

  • 子类必须彻底实现父类的方法

    若是子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、汇集、组合等关系代替继承。
  • 子类能够有本身的个性
  • 覆盖或实现父类的方法时输入参数能够被放大(重载而非覆盖)
  • 覆写或实现父类的方法时输出结果能够被缩小

    父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。

最佳实践

在项目中,采用里氏替换原则时,尽可能避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的“个性”被抹杀——委屈了点;把子类单独做为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺少类替换的标准。

三、依赖倒置原则(Dependence Inversion Principle,DIP)

定义

High level modules should not depend upon low level modules.Both should depend uponabstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

  • 高层模块不该该依赖低层模块,二者都应该依赖其抽象;
  • 抽象不该该依赖细节;
  • 细节应该依赖抽象。

依赖倒置原则在Java中的体现:

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

更加精简的定义就是“面向接口编程”。

依赖的三种写法

  • 构造函数传递依赖对象(构造函数注入),在类中经过构造函数声明依赖对象
  • Setter方法传递依赖对象(Setter依赖注入),在抽象中设置Setter方法声明依赖关系
  • 在接口的方法中声明依赖对象(接口注入)

最佳实践

依赖倒置原则的本质就是经过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,咱们怎么在项目中使用这个规则呢?只要遵循如下的几个规则就能够:

  • 每一个类尽可能都有接口或抽象类,或者抽象类和接口二者都具有

    这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置
  • 变量的表面类型尽可能是接口或者是抽象类
  • 任何类都不该该从具体类派生
  • 尽可能不要覆写基类的方法
  • 结合里氏替换原则使用

    父类出现的地方子类就能出现,再DIP,咱们能够得出这样一个通俗的规则: 接口负责定义public属性和方法,而且声明与其余对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,你们只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。

四、接口隔离原则(Interface Segregations Principle, ISP)

定义

接口隔离原则有如下两种定义:

Clients should not be forced to depend upon interfaces that they don't use.

客户端不该该依赖它不须要的接口。

解释:客户端须要什么接口就提供什么接口,把不须要的接口剔除掉,那就须要对接口进行细化,保证其纯洁性;

The dependency of one class to another one should depend on the smallest possible interface.

类间的依赖关系应该创建在最小的接口上。

解释:要求是最小的接口,也是要求接口细化,接口纯洁,与第一个定义一模一样,只是一个事物的两种不一样描述。

归纳:创建单一接口,不要创建臃肿庞大的接口。再通俗一点讲:接口尽可能细化,同时接口中的方法尽可能少。

接口隔离原则 VS 单一职责原则

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

例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,而且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外经过文档约束“不使用的方法不要访问”,按照单一职责原则是容许的,按照接口隔离原则是不容许的,由于它要求“尽可能使用多个专门的接口”。专门的接口指什么?就是指提供给每一个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是创建一个庞大的臃肿的接口,容纳全部的客户端访问。

// TODO 仍是感受差不太多...

规范

  • 接口要尽可能小

    • 要有限度
    • 不能违背单一职责原则(业务上的划分)
  • 接口要高内聚

    高内聚具体到接口隔离原则就是,要求在接口中尽可能少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变动的风险也就越少,同时也有利于下降成本。

  • 定制服务(只提供访问者须要的方法)

    举例说明:一个用于查询用户的接口,应该提供具体的根据用户ID查询或者根据用户名查询等于业务相关的特定接口,而不是提供一个带有Map(或者说相似Mybatis Example)参数的查询接口

  • 接口设计是有限度的

    • 接口的设计粒度越小,系统越灵活;
    • 可是,灵活的同时也带来告终构的复杂化,开发难度增长,可维护性下降,这不是一个项目或产品所指望看到的;
    • 因此接口设计必定要注意适度,这个“度”如何来判断呢?根据经验和常识判断,没有一个固化或可测量的标准。

最佳实践

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽可能使用原子接口或原子类来组装。可是,这个原子该怎么划分是设计模式中的一大难题,在实践中能够根据如下几个规则来衡量:

  • 一个接口只服务于一个子模块或业务逻辑;
  • 经过业务逻辑压缩接口中的public方法,接口时常去回顾,尽可能让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
  • 已经被污染了的接口,尽可能去修改,若变动的风险较大,则采用适配器模式进行转化处理;
  • 了解环境,拒绝盲从。每一个项目或产品都有特定的环境因素,环境不一样,接口拆分的标准就不一样。深刻了解业务逻辑,结合实际状况进行接口设计。

五、迪米特法则(Law of Demeter,LoD)

也称为最少知识原则(Least KnowledgePrinciple,LKP)

定义

一个对象应该对其余对象有最少的了解

通俗地讲,一个类应该对本身须要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我不要紧,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其余的我一律不关心。

含义

  • 只和朋友交流

    迪米特法则还有一个英文解释是:

    Only talk to your immediate friends.

    只与直接的朋友通讯。

    一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种状况(在一种极端的状况下容许出现这种访问,即每个点号后面的返回类型都相同),类与类之间的关系是创建在类间的,而不是方法间,所以一个方法尽可能不引入一个类中不存在的对象,固然,JDK API提供的类除外。

  • 朋友间也是有距离的

    迪米特法则要求类“羞涩”一点,尽可能不要对外公布太多的public方法和非静态的public变量,尽可能内敛,多使用private、package-private、protected等访问权限。

  • 是本身的就是本身的

    在实际应用中常常会出现这样一个方法:放在本类中也能够,放在其余类中也没有错,那怎么去衡量呢?你能够坚持这样一个原则:若是一个方法放在本类中,既不增长类间关系,也对本类不产生负面影响,那就放置在本类中。

  • 谨慎使用Serializable

最佳实践

  • 迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了之后,类的复用率才能够提升。其要求的结果就是产生了大量的中转或跳转类,致使系统的复杂性提升,同时也为维护带来了难度。在采用迪米特法则时须要反复权衡,既作到让结构清晰,又作到高内聚低耦合。
  • 迪米特法则要求类间解耦,但解耦是有限度的,除非是计算机的最小单元——二进制的0和1。那才是彻底解耦,在实际的项目中,须要适度地考虑这个原则,别为了套用原则而作项目。原则只是供参考,若是违背了这个原则,项目也未必会失败,这就须要你们在采用原则时反复度量,不遵循是不对的,严格执行就是“过犹不及”。

六、开闭原则(Open Closed Principle, OCP)

开闭原则是Java世界里最基础的设计原则,它指导咱们如何创建一个稳定的、灵活的系统

定义

Software entities like classes,modules and functions should be open for extension but closed formodifications.

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

换句话说,一个软件实体应该经过扩展来实现变化,而不是经过修改已有的代码来实现变化。

重要性

开闭原则是最基础的一个原则,前面节介绍的五个原则都是开闭原则的具体形态,也就是说前五个原则就是指导设计的工具和方法,而开闭原则才是其精神领袖。换一个角度来理解,依照Java语言的称谓,开闭原则是抽象类,其余五大原则是具体的实现类,开闭原则在面向对象设计领域中的地位就相似于牛顿第必定律在力学、勾股定律在几何学、质能方程在狭义相对论中的地位,其地位无人能及。

如何使用开闭原则

  • 抽象约束

    • 经过接口或抽象类约束扩展,对扩展进行边界限定,不容许出如今接口或抽象类中不存在的public方法;
    • 参数类型、引用对象尽可能使用接口或者抽象类,而不是实现类;
    • 抽象层尽可能保持稳定,一旦肯定即不容许修改。
  • 元数据(metadata)控制模块行为

    何谓元数据?就是用来描述环境和数据的数据,通俗地说就是配置参数,参数能够从文件中得到,也能够从数据库中得到。

  • 制定项目章程(团队约定)
  • 封装变化

    • 将相同的变化封装到一个接口或抽象类中;
    • 将不一样的变化封装到不一样的接口或抽象类中,不该该有两个不一样的变化出如今同一个接口或抽象类中。
    • 封装变化,也就是受保护的变化(protected variations),找出预计有变化或不稳定的点,咱们为这些变化点建立稳定的接口,准确地讲是封装可能发生的变化,一旦预测到或“第六感”发觉有变化,就能够进行封装,23个设计模式都是从各个不一样的角度对变化进行封装的

最佳实践

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

    开闭原则只是精神口号,实现拥抱变化的方法很是多,并不局限于这6大设计原则,可是遵循这6大设计原则基本上能够应对大多数变化。所以,咱们在项目中应尽可能采用这6大原则,适当时候能够进行扩充,例如经过类文件替换的方式彻底能够解决系统中的一些缺陷。你们在开发中比较经常使用的修复缺陷的方法就是类替换,好比一个软件产品已经在运行中,发现了一个缺陷,须要修正怎么办?若是有自动更新功能,则能够下载一个.class文件直接覆盖原有的class,从新启动应用(也不必定非要从新启动)就能够解决问题,也就是经过类文件的替换方式修正了一个缺陷,固然这种方式也能够应用到项目中,正在运行中的项目发现须要增长一个新功能,经过修改原有实现类的方式就能够解决这个问题,前提条件是:类必须作到高内聚、低耦合,不然类文件的替换会引发不可预料的故障。

  • 项目规章很是重要

    若是你是一位项目经理或架构师,应尽可能让本身的项目成员稳定,稳定后才能创建高效的团队文化,章程是一个团队全部成员共同的知识结晶,也是全部成员必须遵照的约定。优秀的章程能带给项目带来很是多的好处,如提升开发效率、下降缺陷率、提升团队士气、提升技术成员水平,等等。

  • 预知变化

    在实践中过程当中,架构师或项目经理一旦发现有发生变化的可能,或者变化曾经发生过,则须要考虑现有的架构是否能够轻松地实现这一变化。架构师设计一套系统不只要符合现有的需求,还要适应可能发生的变化,这才是一个优良的架构。

开闭原则是一个终极目标,任何人包括大师级人物都没法百分之百作到,但朝这个方向努力,能够很是显著地改善一个系统的架构,真正作到“拥抱变化”。

七、总结

首先,不管是6大设计原则仍是23种设计模式,根本目标其实就是一个"拥抱变化",包括需求的变化、运行环境的变化、性能要求的提高等等,实现一个需求并不难,可是当变化来临时,可否泰然处之,那就是个技术活了。

其次,"拥抱变化"落实到代码层面是什么?——你的代码是可维护的、可扩展的,仍是说"牵一发动全身",一点小的改动不少东西都要跟着变更。

再次,要作到"拥抱变化",让你的代码很容易维护和扩展,核心理念就是"高内聚、低耦合",以及"面向接口编程(面向抽象编程)

最后,设计原则、设计模式是前辈总结的"经验",但不是"条款",尽量遵循这些规范会让你的设计无限接近完美,但世界上本就没有十全十美的东西,凡事都要有个度,不要认死理,不要为了"套模式"而应用设计模式,要具体问题具体分析,根据实际状况进行权衡。

设计作得再漂亮,代码写得再完美,项目作得再符合标准,一旦项目亏本,产品投入大于产出,那总体就是扯淡!

参考文献:《设计模式之禅》

相关文章
相关标签/搜索