(1)单一职责原则(Single Responsibility Principle)
定义:
一个类/接口只负责一项职责
描述:
当一个职责变更需要修改类的时候,应该避免影响到其它职责的功能,因此要遵从单一职责原则,将职责进行拆分。
问题:
尽管这个原则在平时工作中大多数情况都能遵守,但是仍有可能违背这一原则。原因在于,存在职责扩散,或者说叫职责细分。即因为某种原因,一个职责被划分为粒度更细的职责1,2...
举例:
类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)
级别:
原则往往不是绝对的,单一职责原则可分为代码级别,方法级别,类级别。有时候尽管违背了类级别的单一职责原则,但是保证了方法的单一职责原则,也不是不可以。只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则。
好处:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
一个不好的例子:
一个方法,里面放了多个业务逻辑,都很长,这就违背了方法的单一职责原则,需要拆成多个方法
一个方法,里面有一个if else,虽然是做一件事,但是每种情况处理不同的复杂业务,代码量很大,比如,如果A,则写ES和HBase,如果B,则写到Mysql,这就是代码级别上没有遵守单一职责,可以拆成2个子方法
(2)里氏替换原则(Liskov Substitution Principle)
定义:
在使用基类的地方,可以透明/任意地使用其子类
描述:
子类可以扩展父类的功能,但不能改变父类原有功能:子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法;子类可以增加自己持有的方法;当子类的方法重载父类的方法时,方法的形参要比父类的输入参数更加宽松;子类方法实现父类抽象方法时,返回值要比父类更严格。
问题:
里氏替换原则主要发力点是继承基础上的抽象和多态,这里要注意重写(Override)与重载(Overload)的区分,即使参数的数据范围发生变化,也能将重写变成重载!而你原本只是想把所继承的方法完善的具体点儿!如果是这样的话绝对会引起以后业务逻辑的混乱。
好处:
出问题的概率会减小
(3)依赖倒置原则(Dependency Inversion Principle)
含义:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
了解Spring对这里应该不陌生。相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
要点:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
- 使用继承时遵循里氏替换原则。
关于“倒置”这个词。准确的讲,这是因为传统的软件开发方法,如结构化的分析和设计,倾向于创建高层模块依赖于低层模块、抽象依赖于具体的软件结构。在实际上,这些方法的目标之一就是去定义描述上层模块如何调用低层模块的子程序层次结构。所以,相对于传统的过程化的方法通常所产生的那种依赖结构,一个设计良好的面向对象的程序中的依赖结构就是“被倒置”的。
在系统代码的实现中,依赖关系一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标抽象的稳定性决定了系统的稳定性,因为抽象是不变的;依赖倒置原则的运用还可以减少并行开发引起的风险,提高代码的可读性和维护性。依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
(4)接口隔离原则(Interface Segregation Principle)
一个接口内的方法不要过多,以免实现类不得不实现不需要的方法。接口隔离原则的核心精神是:尽量使用多个专门的单一的小接口,避免庞大的总接口;专业点的说法是类间的依赖关系尽量建立在最小的接口上。这样做的意义就是减小依赖,接口尽量的小,但是小不是说一个接口一个方法,小也s要不违背单一职责原则;接口应该严格体现内聚性,尽可能的少公布public方法,在开发中不能让功能繁琐的大接口出现;一个类对另一个类的依赖应该建立在最小的接口上,不能强迫与之无关的方法,否则将会造成接口污染。
采用接口隔离原则对接口进行约束时,要注意以下几点:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
不好的例子:
- 加解密服务,一个dubbo接口提供一大堆方法,有些实际上是有区别的,比如解密明文和获取相关信息的就应该分开,信息的这种不该算作解密
(5)迪米特法则(Low of Demeter)
定义:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
迪米特法则也叫做做最少知识原则,其核心精神是:不和陌生人说话,通俗之意是一个对象对自己需要耦合关联调用的类应该知道的更少。这样会导致类之间的耦合度降低,每个类都尽量减少对其他类的依赖,因此,这也很容易使得系统的功能模块相互独立,之间不存在很强的依赖关系。
耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
(6)开闭原则(Open Close Principle)
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
对扩展开放,对修改关闭。
开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。
其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。
回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。