一、开闭原则
- 概念:
~~~~~~ 一个软件实体,如类、模块和函数,应该对扩展开放,对修改关闭。
~~~~~~ 所谓开闭,也正是对扩展和修改两个行为的一个原则。
~~~~~~ 核心:面向抽象编程:用抽象构建框架,用实现扩展细节,提高软件的可复用性和可维护性 - 例子:利用IOC框架(Spring)动态创建对象
~~~~~~
二、依赖倒置原则(Dependence Inversion Principle)
- 概念:
~~~~~~ 高层模块不应该依赖底层模块
~~~~~~ 两者都应该依赖其抽象,抽象不应该依赖其细节
~~~~~~ 细节应该依赖抽象:
~~~~~~ ~~~~~~ 1) 低层模块:不可分割的逻辑
~~~~~~ ~~~~~~ 2) 高层模块:逻辑的再组装 - 什么是抽象:
~~~~~~ 抽象就是指接口或抽象类,两者都是不能直接被实例化的 - 什么是细节:
~~~~~~ 细节就是实现类,实现接口或继承抽象类而产生的类就是细节 - 依赖倒置原则:
~~~~~~ i. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
~~~~~~ ii. 接口或抽象类不依赖于实现类;
~~~~~~ iii. 实现类依赖接口或抽象类。 - IOC控制反转:
~~~~~~ 对象的控制权进行转移,例如,转移交给了IoC容器,它就是一个创建工厂。
~~~~~~ ~~~~~~ 1) 需要的对象可以通过配置文件创建
~~~~~~ ~~~~~~ 2) 依赖关系发生变化:原先的依赖关系消失,因为它们都依赖IoC容器。通过IoC容器建立它们之间的关系
~~~~~~ 依赖注入(DI):所谓依赖注入,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中 - 总结:
~~~~~~ i. 依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合
~~~~~~ ii. 依赖倒置的基本要求:
~~~~~~ ~~~~~~ 1) 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
~~~~~~ ~~~~~~ 2) 接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置
~~~~~~ iii. 变量类型尽量是接口或者是抽象类
~~~~~~ iv. 任何类都不应该从具体类派生。(不一定)
~~~~~~ v. 尽量不要覆写基类的方法
~~~~~~ ~~~~~~ 1) 如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生一定的影响。
~~~~~~
三、单一职责原则(Simple Responsibility Principle, SRP)
- 遵循单一职责原的优点有:
~~~~~~ i. 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多。
~~~~~~ ii. 提高类的可读性,提高系统的可维护性。
~~~~~~ iii. 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
~~~~~~ iv. 需要说明的一点是单一职责原则不只适用于面向对象编程思想,只要是模块化的程序设计,都适用单一职责原则。 - 缺点:
~~~~~~ 最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
~~~~~~
四、接口隔离原则(Interface Segregation Principle)
- 定义:用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
- 我们在设计接口时应当注意以下几点:
~~~~~~ i. 一个类对一类的依赖应该建立在最小的接口之上。
~~~~~~ ii. 建立单一接口,不要建立庞大臃肿的接口。
~~~~~~ iii. 尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)。 - 优点:
~~~~~~ 接口隔离原则符合我们常说的高内聚、低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。在设计接口的时候,要多花时间去思考,要考虑业务模型,包括以后有可能发生变更的地方还要做一些预判。
~~~~~~
五、迪米特法则(Law of Demeter, LoD)
- 定义:一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle, LKP),尽量降低类与类之间的耦合。
- 迪米特原则:主要强调只和朋友交流,不和陌生人说话。出现在成员变量、方法的输入、输出参数中的类都可以称之为成员朋友类,而出现在方法体内部的类不属于朋友类。
- 优点:
~~~~~~ i. 降低了类之间的耦合度,提高了模块的相对独立性。
~~~~~~ ii. 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。 - 缺点:
~~~~~~ 过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
~~~~~~
六、里氏替换原则
- 定义:
~~~~~~ i. 如果对每一个类型为 T1 的 对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换 成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
~~~~~~ ii. 一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。 - 引申含义:子类可以扩展父类的功能,但不能改变父类原有的功能。
~~~~~~ i. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
~~~~~~ ii. 子类中可以增加自己特有的方法。
~~~~~~ iii. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类 方法的输入参数更宽松。
~~~~~~ iv. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即 方法的输出/返回值)要比父类更严格或相等。
~~~~~~ 好比开闭原则的案例时候在获取打折后时重写覆盖了父类的 getPrice()方法,增加了一个获取原价的方法 getOriginPrice(),显然就违背了里氏替换原则。我们修改一下代码,不应该覆盖 getPrice()方法,增加 getDiscountPrice ()方法。
~~~~~~
七、合成复用原则(Composite Reuse Principle, CRP)
- 定义:
~~~~~~ i. 又叫组合/聚合复用原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
~~~~~~ ii. 如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。 - 复用分为继承复用和合成复用两种
~~~~~~ i. 继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
~~~~~~ ~~~~~~ 1) 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
~~~~~~ ~~~~~~ 2) 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
~~~~~~ ~~~~~~ 3) 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
~~~~~~ ii. 组合或聚合复用:将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
~~~~~~ ~~~~~~ 1) 维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
~~~~~~ ~~~~~~ 2) 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
~~~~~~ ~~~~~~ 3) 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。 - 合成复用原则的实现方法:
~~~~~~ 合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
~~~~~~
总结
七种设计原则:
开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
单一职责原则告诉我们实现类要职责单一;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合度;
合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
目的只有一个:降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。
记忆口诀:访问加限制,函数要节俭,依赖不允许,动态加接口,父类要抽象,扩展不更改。
在程序设计时,我们应该将程序功能最小化,每个类只干一件事。若有类似功能基础之上添加新功能,则要合理使用继承。对于多方法的调用,要会运用接口,同时合理设置接口功能与数量。最后类与类之间做到低耦合高内聚。