6大设计原则
- 单一职责原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特法则
- 开闭原则
1 单一职责原则(Single Responsibility Principle)
单一职责,简称SRP。单一职责的定义是:应该有且仅有一个原因引起类的改变。单一职责最难划分的就是职责,一个职责一个接口,但是职责的划分因项目而异。对于接口,我们在设计的时候一定要做到单一,但是对于实现类尽量做到只有一个原因引起变化。实现类生搬硬套单一职责会引起类的剧增。
2 里氏替换原则(Liskov Substitution Principle)
里氏替换原则,简称LSP。通俗来讲,只要父类能出现的地方子类就能出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本不需要知道是父类还是子类。但是有子类出现的地方,父类未必能适应。
里氏替换原则包含四重含义:
1) 子类必须完全实现父类的方法。在类中调用其他类时务必使用父类或借口,如果不能使用父类或借口,则说明违背了LSP原则。
2) 子类可以有自己的个性。
3) 覆盖或实现父类的方法时参数可以被放大。里氏替换原则要求制定一个契约,就是父类或者借口,这种设计方法也叫做Design by Contract (契约设计),与里氏替换原则有着异曲同工之妙。子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。
4) 覆写或实现父类的方法时输出结果可以被缩小。
3 依赖倒置原则(Dependence Inversion Principle)
依赖倒置原则,简称DIP。其含义为:高层模块不应依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。更加精简的定义就是“面向接口编程”。
两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立得运行,而TDD (Test-Driven Development,测试驱动开发) 开发模式就是依赖倒置原则的最高级应用。
抽象是对实现的约束,对依赖者而言,也是一种契约。不仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展。
依赖传递的三种方法:
1) 构造函数传递以来对象。在类中通过构造函数声明依赖对象,按照依赖注入的说法这种方式叫做构造函数注入。
2) Setter方法传递依赖对象。在抽象中设置Setter方法声明依赖关系,也可称为Setter依赖注入。
3) 接口声明依赖对象,也称为接口注入。
依赖倒置原则的本质就是通过抽象(接口或者抽象类)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。我们可以遵循以下规则:每个类尽量有接口或者抽象类,或者两者都具备;变量的声明类型尽量是接口或者抽象类;任何类都不应从具体类派生;尽量不要覆写基类方法;结合里氏替换原则可以得到如下规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确地实现业务逻辑,同时在适当的时候对父类进行细化。
4 接口隔离原则(Interface Segregation Principle)
接口隔离原则,其含义为:客户端应该依赖它需要的接口,仅提供需要的接口,剔除不需要的,细化需要的接口,保证其纯洁性。接口中的方法应该尽量少。
单一职责要求的是类和接口职责单一,注重的是职责,这是逻辑业务上的划分,而接口隔离原则要求接口方法尽量少。
保证接口的纯洁性要求
1) 接口尽量小。根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
2) 接口要高内聚。在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对开发越有利,变更风险越小,同时有利于降低成本。
3) 定制服务。单独为一个个体提供优良的服务,只提供访问者需要的方法。
4) 接口设计是有限度的。接口设计的粒度要适度。
5 迪米特法则(Law of Demeter)
迪米特法则,简称LoD,也被称为最少知识原则(Least Knowledge Principle,LKP)。一个类应该对自己耦合或调用的类知道得最少。
迪米特法则要求尽量不要对外公布太多的public方法和非静态的public方法,多使用private、package-private、protected等访问权限。
如果一个方法放在本类中,既不增加类间关系,也不对本类产生负面影响,那么就放置在本类中。
迪米特法则的核心就是类间解耦,弱耦合。只有弱耦合以后,类的复用率才能提高。其要求的结果就是产生大量的中转或者跳转类,导致系统的复杂度提高,同时也为维护带来难度。
6 开闭原则(Open Closed Principle)
开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块修改必然有高层模块耦合,否则就是一段无意义的代码。
变化可以归纳为三种类型:
1) 逻辑变化。只变化一个逻辑,而不涉及其他模块,可以通过修改原有类中的方法来实现。前提条件是所有依赖或者关联类都按照相同的逻辑处理。
2) 子模块变化。低层次模块的变化必然引起高层次模块的变化。因此在通过扩展完成变化时,高层次的模块修改是必然的。
3) 可见视图的变化。
如何使用开闭原则:
1) 抽象约束。通过接口或抽象类可以约束一组可能变化的行为,并且能够实现开放。其包含三层含义:第一,通过接口和抽象类对扩展进行边界的限定,不允许出现在接口或抽象类中不存在的public方法;第二,参数类型、引用对象尽量使用接口或者抽象类而不是实现类;第三,抽象层保持稳定,一旦确定就不允许修改。
2) 元数据(metadata)控制模块行为。
3) 制定项目章程。
4) 封装变化。