软件的可维护性和可复用性时两个非常重要的用于衡量软件质量的属性,通过在软件开发中使用这些原则可以提高软件的可维护性和可复用性,以便设计出兼具良好的可维护性和可复用性的软件系统,实现可维护性复用的目标
一丶单一职责原则(Single Responsiblity Principle(SRP))
使用频率:⭐⭐⭐⭐
单一职责原则是最简单的面向对象设计原则,用于控制类的粒度大小
定义:一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中,它的原则是实现高内聚,低耦合的指导方针
在软件系统中,一个类(小到方法,大到模块)承担的职责越多,它被复用的可能性就越小,一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中的一个职责发生变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,如果多个职责总是同时发生改变,则可以将它们封装在同一类中。
二丶开闭原则(Open-Closed Priciple(OCP))
使用频率:⭐⭐⭐⭐⭐
开闭原则是面向对象的可复用设计的第一块基石,是最重要的面向对象设计原则
定义:软件实体应对扩展开放,对修改关闭。
任何软件都需要面临一个重要的问题,即他们的需求会随着事件的推移发生变化,当软件系统需要面对新的需求时,应该尽量保证系统的设计框架是稳定的,如果一个软件设计符合开闭原则,可以方便的对系统进行扩展,而且扩展无需修改现有代码
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键,在23种设计模式种,大部分设计模式都符合开闭原则,在对每一个模式进行优缺点评价时都会将开闭原则作为一个重要的评价依据,以判断基于该模式设计的系统是否具有良好的灵活性和可拓展性。
三丶里氏代换原则(Liskov Subtitution Priciple(LSP))
使用频率:⭐⭐⭐⭐⭐
里氏代替原则是实现开闭原则的基础
定义:所有引用基类的地方必须能透明地使用其子类对象
在软件种将一个基类对象替换成它地子类对象,程序将不会产生任何错误和异常,反之亦然。如果一个软件实体使用的是一个子类对象,那么它一定能够使用其基类对象,例如我喜欢所有动物,那我一定喜欢狗,但是如果说我喜欢动狗,不能说我喜欢所有动物。
在运用里氏代替原则时,应该将父类设计为抽象类或接口,让子类继承父类或实现父类接口,并实现在父类中声明方法。运行时,子类实例替换父类实例,可以很方便的扩展系统的功能,无需修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现
四丶依赖倒转原则(Dependency Inversion Priciple(DIP))
使用频率:⭐⭐⭐⭐⭐
如果说开闭原则是面向对象设计的目标,那么依赖倒转原则就是面向对象设计的主要实现机制之一,它是系统抽象化的具体实现。
定义:高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象,简单来说,它要求针对接口编程,而不要针对实现编程
依赖倒转原则要求,在程序代码种传递参数或在关联关系中尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明,参数类型声明,方法返回类型声明,以及数据类型转换等,而不要用具体的类来做这些事情, 为了确保该原则的应用,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加新方法
在引入抽象层后, 系统将具有很好的灵活性,在程序中应该尽量使用抽象层进行编程,而将具体类邪贼配置文件中,这样一来,系统行为发生变化只需要对抽象层进行拓展,并修改配置文件,而不许修改原有系统的源代码
在大多数情况下,开闭原则,里氏代替原则,依赖倒转原则三个是同时出现,开闭是目标,里氏原则是基础,依赖倒转是手段,它们相辅相成,目标一致,只是分析问题时所在角度不同
五丶接口隔离原则(Interface Segregation Priciple(ISP))
使用频率:⭐⭐
定义:客户端不应该依赖哪些它不需要的接口
根据接口隔离原则,当一个接口太大时,需要将它切割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可,它有两种理解
1:当把“接口”理解成一个类型所提供的所有方法特征的方法的集合时,这是一种逻辑上的概念,这时可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的接口,这个原则可以成为角色隔离原则。
2:当把“接口”理解成狭义的特定语言的接口,那么这原则是指接口仅仅提供客户端需要的行为,对于客户端不需要的行为需要隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的接口,在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的接口使用起来总是不方便,但是接口也不能太小,太小会导致系统中接口泛滥,不利于维护
六丶合成复用原则(Composite Reuse Priciple(CRP))
使用频率:⭐⭐⭐⭐
定义:优先使用对象组合,而不是继承来达到目的
合成复用原则是在一个新的对象里通过关联关系(组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分,复用时要尽量使用组合/聚合关系(关联),少用继承。
通过继承来进行复用的主要问题在于,继承会破坏系统的封装性,因为继承会将基类的实现细节暴漏给子类,由于基类的内部细节对子类来说是可见的,所以这种复用被成为“白箱”复用,如果基类发生变化,那么子类的实现也不得不发生变化,从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性
通过组合/聚合的关系可以将已有的对象纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使成员对象的内部实现细节对于新对象不可见,所以这种复用被成为“黑箱”复用,相对继承,其耦合度比较低,成员对象的变化对于新对象影响不大,也可以在运行时动态进行,有较强的灵活性
七丶迪米特法则(Low of Demeter(LOD))
使用频率:⭐⭐⭐
定义:每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的单位
迪米特法则要求一个软件实体应当尽可能地少与其他实体发生相互作用,如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是软件实体之间通信的限制,迪米特法则要求限制软件实体之间的宽度和深度,应用迪米特法则可以降低系统的耦合度,使类和类之间保持松散的耦合关系。
迪米特法则的“朋友”
1:当前对象本身(this)
2:以参数形式传入当前对象方法中的对象
3:当前对象的成员对象
4:如果当前对象的成员对象时一个集合,那么集合中的元素也是朋友
5:当前对象所创建的对象
那么一个对象只能与直接朋友发生交互,不要和“陌生人”发生直接交互,这样做可以降低系统的耦合程度,一个对象的改变不会给太多其他对象带来影响。
在系统设计中,类的划分应当尽量创建松散耦合的类,类之间的耦合程度越低,越有利于复用;在类的结构设计上,每个类都应当尽可能降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。