内容:
就一个类而言,应该仅有一个引起它变化的原因。
问题由来:
T类包括职责T1、职责T2,当由于T1变化需要修改T类时,原本正常工作的T2无法工作。
产生原因:
职责扩散:某一职责被分为颗粒度更小的多个职责。
解决办法:
将不同职责封装到不同的模块或者类中。
目的就是解耦:
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化就可能抑制或者削弱这个类完成其他职责的能力。
-----------------------------------------------------------------------------
O:开闭原则(OCP:Open-Close Priciple)
核心思想:
对扩展开放:有新的需求变化时,可以对现有代码进行扩展,以适应新的情况。
对修改关闭:类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
如何实现:
基础:Liskov替换、合成/聚合复用原则
实现原理:抽象编程。因为抽象相对稳定,让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为。
重要手段:封装变化。
-----------------------------------------------------------------------------
L:里氏代换(LSP:The Liskov Substitution Principle)
内容:
子类型必须能够替换掉它们的父类型。
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
应用技巧:
在程序中尽量使用基类来对对象进行定义,而在运行时再确定其子类类型,用子类对象替换父类对象。
注意事项:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
-----------------------------------------------------------------------------
I:接口隔离原则(ISP:The Interface Segregation priciple)
定义:
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:
类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:
将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
注意事项:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
- 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
与单一职责原则的区别:
1、单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离
2、单一职责原则主要是约束类,其次才是接口和方法,他针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建
-----------------------------------------------------------------------------
D:依赖倒转(DIP:The Dependency InVersion Principle)
内容:
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体,具体应该依赖于抽象。
依赖倒置原则基于这样一个事实:
相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。
问题由来:
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:
将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
在实际编程中,我们一般需要做到如下3点:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
- 使用继承时遵循里氏替换原则。
依赖倒置有三种方式来实现:
1、通过构造函数传递依赖对象;
比如在构造函数中的需要传递的参数是抽象类或接口的方式实现。
2、通过setter方法传递依赖对象;
即在我们设置的setXXX方法中的参数为抽象类或接口,来实现传递依赖对象。
3、接口声明实现依赖对象,也叫接口注入;
即在函数声明中参数为抽象类或接口,来实现传递依赖对象,从而达到直接使用依赖对象的目的。
实质:
依赖倒置原则其本质就是契约式编程(面向接口编程),通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。如果没有实现这个原则,那么也就意味着开闭原则(对扩展开发,对修改关闭)也无法实现。