面向对象设计原则概述
面向对象的设计中,最重要的两个方面就是如何提高软件的可复用性及可维护性。这也是软件架构师或软件设计师最重要的工作,也是最难的工作。软件需求的变化是软件设计人员最不愿看到的,但却是实实在在存在的,软件中唯一不变的东西就是变化,换句话说,变化是必然的。因此,分装变化就是最重要的工作了。
以下几点需要注意:
针对接口编程,而不是针对实现编程
客户无需知道所使用对象的特定类型,只需知道对象拥有客户所期望
的接口
有限使用组合,而不是类继承
类继承通常为“白箱复用”,对象组合通常成为“黑箱复用”。继
承在某种程度上破坏了封装性,子类父类耦合度高;而对象组合则只
要求被组合对象具有良好定义的接口,耦合度低。
封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧
进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦
合。
常用的面向对象设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充。
j
1.单一职责原则
单一职责原则分析
一个类(或者大到模块,小到方法)承担的职责越多,它被复用的
可能性越小,而且如果一个类承担的职责过多,就相当于将这些职
责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运
作。
类的职责主要包括两个方面:数据职责和行为职责,数据职责通过
其属性来体现,而行为职责通过其方法来体现。
单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构
手法中都能找到它的存在,它是最简单但又最难运用的原则,需要
设计人员发现类的不同职责并将其分离,而发现类的多重职责需要
设计人员具有较强的分析设计能力和相关重构经验。
2.开闭原则
开闭原则定义
开闭原则(Open-Closed Principle, OCP)定义如下:
? 一个软件实体应当对扩展开放,对修改关闭。也就是说在设计
一个模块的时候,应当使这个模块可以在不被修改的前提下被
扩展,即实现在不修改源代码的情况下改变这个模块的行为。
开闭原则分析
抽象化是开闭原则的关键。
开闭原则还可以通过一个更加具体的“对可变性封
装原则”来描述,对可变性封装原则(Principle of
Encapsulation of Variation, EVP)要求找到系统
的可变因素并将其封装起来。
一般而言,无论模块多么“封闭”,都会存在一些无法
对之封闭的变化。没有对所有情况都贴切的模型。
设计人员必须对于他设计的模块应对哪种变化封闭做出选
择。必须先猜测出最有可能发生变化的种类,然后构造抽
象来隔离那些变化。
有时不容易做到,代价有时会比较高,我们希望把OCP应
用限定在可能发生的变化上。
开发人员应该仅仅对程序中呈现出频繁变化的那些部分做
出抽象。拒绝不成熟的抽象和抽象本身一样重要。(对变
化进行封装)。
3.里氏代换原则
里氏代换原则定义
里氏代换原则(Liskov Substitution Principle, LSP)有两种定义方式,
第一种定义方式相对严格,其定义如下:
? 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有
程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,那么类型S是类
型T的子类型。
其英文定义为:
? If for each object o1 of type S there is an object o2 of type T such that for all
programs P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2 then S is a subtype of T.
第二种更容易理解的定义方式如下:
? 所有引用基类(父类)的地方必须能透明地使用其子类的对象。
其英文定义为:
? Functions that use pointers or references to base classes must be able to use
objects of derived classes without knowing it.
里氏代换原则分析
里氏代换原则可以通俗表述为:在软件中如果能够使用
基类对象,那么一定能够使用其子类对象。把基类都替
换成它的子类,程序将不会产生任何错误和异常,反过
来则不成立,如果一个软件实体使用的是一个子类的话,
那么它不一定能够使用基类。
里氏代换原则是实现开闭原则的重要方式之一,由于使
用基类对象的地方都可以使用子类对象,因此在程序中
尽量使用基类类型来对对象进行定义,而在运行时再确
定其子类类型,用子类对象来替换父类对象。
4.依赖倒转原则
依赖倒转原则定义
依赖倒转原则(Dependence Inversion Principle, DIP)的定义
如下:
? 高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依
赖于细节,细节应该依赖于抽象。
其英文定义为:
? High level modules should not depend upon low level modules, both
should depend upon abstractions. Abstractions should not depend
upon details, details should depend upon abstractions.
另一种表述为:
? 要针对接口编程,不要针对实现编程。
其英文定义为:
? Program to an interface, not an implementation.
依赖倒转原则分析
简单来说,依赖倒转原则就是指:代码要依赖于抽象的
类,而不要依赖于具体的类;要针对接口或抽象类编程,
而不是针对具体类编程。
实现开闭原则的关键是抽象化,并且从抽象化导出具体
化实现,如果说开闭原则是面向对象设计的目标的话,
那么依赖倒转原则就是面向对象设计的主要手段。
类之间的耦合
? 零耦合关系
? 具体耦合关系
? 抽象耦合关系
依赖倒转原则要求客户端依赖于抽象耦合,以抽象方
式耦合是依赖倒转原则的关键。
5.接口隔离原则
接口隔离原则定义
接口隔离原则(Interface Segregation Principle, ISP)的定义如下:
? 客户端不应该依赖那些它不需要的接口。
其英文定义为:
? Clients should not be forced to depend upon interfaces that they do
not use.
注意,在该定义中的接口指的是所定义的方法。
另一种定义方法如下:
? 一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接
口的客户端仅需知道与之相关的方法即可。
其英文定义为:
? Once an interface has gotten too 'fat' it needs to be split into
smaller and more specific interfaces so that any clients of the
interface will only know about the methods that pertain to them.
接口隔离原则分析
使用接口隔离原则拆分接口时,首先必须满足单一职
责原则,将一组相关的操作定义在一个接口中,且在
满足高内聚的前提下,接口中的方法越少越好。
可以在进行系统设计时采用定制服务的方式,即为不
同的客户端提供宽窄不同的接口,只提供用户需要的
行为,而隐藏用户不需要的行为。
合成复用原则定义
合成复用原则(Composite Reuse Principle, CRP)又称为组合/
聚合复用原则(Composition/ Aggregate Reuse Principle,
CARP),其定义如下:
? 尽量使用对象组合,而不是继承来达到复用的目的。
其英文定义为:
? Favor composition of objects over inheritance as a reuse
mechanism.
6.合成复用原则
合成复用原则分析
组合/聚合可以使系统更加灵活,类与类之间的耦合度
降低,一个类的变化对其他类造成的影响相对较少,
因此一般首选使用组合/聚合来实现复用;其次才考虑
继承,在使用继承时,需要严格遵循里氏代换原则,
有效使用继承会有助于对问题的理解,降低复杂度,
而滥用继承反而会增加系统构建和维护的难度以及系
统的复杂度,因此需要慎重使用继承复用
7.迪米特法则
迪米特法则定义
迪米特法则(Law of Demeter, LoD)又称为最少知识原则(Least
Knowledge Principle, LKP),它有多种定义方法,其中几种典
型定义如下:
(1) 不要和“陌生人”说话。英文定义为:Don't talk to strangers.
(2) 只与你的直接朋友通信。英文定义为:Talk only to your
immediate friends.
(3) 每一个软件单位对其他的单位都只有最少的知识,而且局限于那
些与本单位密切相关的软件单位。英文定义为:Each unit should
have only limited knowledge about other units: only units "closely"
related to the current unit.
迪米特法则的主要用途在于控制信息的过载:
? 在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越
低,就越有利于复用,一个处在松耦合中的类一旦被修改,不
会对关联的类造成太大波及;
? 在类的结构设计上,每一个类都应当尽量降低其成员变量和成
员函数的访问权限;
? 在类的设计上,只要有可能,一个类型应当设计成不变类;
? 在对其他类的引用上,一个对象对其他对象的引用应当降到最
低。
小结
对于面向对象的软件系统设计来说,在支持可维护性的同
时,需要提高系统的可复用性。
软件的复用可以提高软件的开发效率,提高软件质量,节
约开发成本,恰当的复用还可以改善系统的可维护性。
单一职责原则要求在软件系统中,一个类只负责一个功能
领域中的相应职责。
开闭原则要求一个软件实体应当对扩展开放,对修改关闭,
即在不修改源代码的基础上扩展一个系统的行为。
里氏代换原则可以通俗表述为在软件中如果能够使用基类
对象,那么一定能够使用其子类对象。