『单一职责原则』
英文名:Single Responsibility Principle (SRP)
别称:单一功能原则
就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。
此原则的核心就是解耦和增强内聚性。
单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。
产生原因:(职责扩散)因为某种原因,某一职责被分化为颗粒度更细的多个职责了。
「问题由来」
ClassA负责两个不同的职责:职责P1和职责P2。当因职责P1需求发生改变而修改ClassA时,可能会导致原本运行正常的职责P2功能发生故障。也就是说职责P1和职责P2被耦合在一起了。
「解决办法」
遵守单一职责原则,将不同的职责封装到不同的类或模块中。
软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。判断是否应该分离,就是如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑类的职责分离。
- 只要逻辑足够简单,才可以在代码级别上违反单一职责原则;
- 只有类中的方法数量足够少,才可以在方法级别上违反单一职责原则。
- 实际应用中,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。
「优点」
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
『里氏替换原则』
英文名:Liskov Substitution Principle (LSP)
任何基类可以出现的地方,子类一定可以出现。
LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
「问题由来」
有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
「解决办法」
当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
- 创建一个新的抽象类C,作为两个具体类的超类,将A,B的共同行为移动到C中来解决问题。
- 从B到A的继承关系改为委派、依赖、聚合,组合等关系代替。
在进行设计的时候,尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
「优点」
- 减少对象间的耦合性;
- 程序的可移植性高;
- 提高系统的可维护性。
继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。
『依赖倒置原则』
英文名:Dependence Inversion Principle(DIP)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;
抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。
以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。
使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒置原则的核心思想是面向接口编程。
现在很流行的TDD开发模式就是依赖倒置原则最成功的应用。
「问题由来」
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A(负责完成主要的业务逻辑),引入错误的风险极大。
「解决办法」
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
传递依赖关系有三种方式,接口传递、构造方法传递、setter方法传递。
在实际编程中,我们一般需要做到如下3点:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
- 使用继承时遵循里氏替换原则。
「优点」
- 可以降低类之间的耦合性;
- 提高系统的稳定性;
- 降低修改程序造成的风险;
- 给多人并行开发带来了极大的便利。
『接口隔离原则』
英文名:Interface Segregation Principle(ISP)
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。
接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
「问题由来」
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
「解决办法」
将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
采用接口隔离原则对接口进行约束时,要注意以下几点:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
「优点」
- 可以降低类之间的耦合性;
- 提高系统的灵活性和可维护性。
「接口隔离原则 VS 单一职责原则」
- 单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
- 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
『迪米特原则』!!!有些也说设计模式六大原则。。。
英文名:Law of Demeter(LoD)
别称:最少知识原则 or 最少知道原则
一个软件实体应当尽可能少的与其他实体发生相互作用。
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。
通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
迪米特法则还有一个更简单的定义:只与直接的朋友通信(talk only to your immediate friends)。每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
「问题由来」
类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系。过分的使用迪米特原则,会产生大量的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。
「解决办法」
尽量降低类与类之间的耦合。
遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
门面模式和调停者模式(中介者模式)实际上就是迪米特法则的应用。
「优点」
- 可以降低类之间的耦合性。
『开闭原则』
英文名:Open Closed Principle(OCP)
别称:开放封闭原则
对于扩展是开放的;对于更改是封闭的。
Open for extension; Closed for modification.
我们遵循设计模式的其他原则,以及使用23中设计模式的目的就是遵循开闭原则。
也就是说,只要我们对其他原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是五项原则遵守成都的“平均得分”,其他原则遵守的好,平均分自然就高,说明软件开闭原则遵守的好;如果其他原则遵守的不好,则说明开闭原则遵守的不好。
开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。
「问题由来」
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
「解决办法」
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
『总结』
开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
- 单一职责原则:告诉我们实现类要职责单一;
- 里氏替换原则:告诉我们不要破坏继承体系;
- 依赖倒置原则:告诉我们要面向接口编程;
- 接口隔离原则:告诉我们在设计接口的时候要精简单一;
- 迪米特法则:告诉我们要降低耦合;
- 开闭原则:是总纲,告诉我们要对扩展开放,对修改关闭。
对这几个原则的遵守,不是是和否的问题,而是多和少的问题。任何事都是过犹不及,制定这六个原则的目的并不是要我们刻板遵守他们,而是要根据实际情况灵活运用。对他们的遵守成都只要在一个合理的范围内,就算是良好的设计。
- 设计1+设计2 属于良好的设计,对六项原则的遵守成都属于合理的范围;
- 设计3+设计4 有些不足,但是基本可以接受;
- 设计5 严重不足,对各项原则都没有很好的遵守;--需要重构;
- 设计6 遵守过度;--需要重构;