oo之六大设计原则&&Solid原则
2012-11-26 20:14 youxin 阅读(470) 评论(0) 编辑 收藏 举报一、单一职责原则:英文名称是Single Responsibility Principle,简称SRP。有且只有一个原因引起类的变更。There should never be more than one reason for a class to change.
例如在电话类的设计中,接口包含三个方法:拨号,通话和挂电话。但是这个接口包含了两个职责,拨号和挂电话属于协议管理,通话属于数据传输。不符合单一职责原则。可以将拨号和挂电话作为一个接口,通话作为一个接口,一个类实现了这两个接口,把两个职责融合在一个类中,对外公布的是接口,而不是实现类。
优点:类的复杂度降低,提高了可读性,可维护性,降低了变更引起的风险。
单一职责不仅适用于接口,类,还适用于方法,尽量使每个方法的职责清晰。
二、里氏替换原则:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. 所有引用基类的地方必须透明地使用其子类的对象。
通俗的讲,只要父类出现的地方,子类都可以出现,而且替换为子类也不会发生任何异常或错误,使用者根本不需要知道是子类还是父类。包含四层含义:
1.子类必须完全实现父类的方法。
2.子类可以有自己的方法。
3.覆盖或实现父类的方法时,输入参数可以放大。例如父类有一个的doSomething方法,其参数为(HashMap),而子类中重载了这个方法,参数为(Map),因为参数不一样,所以不是覆写,而是重载,此时,当子类当做父类使用时,传入HashMap参数,执行的为父类的方法。若反过来,传入HashMap时,执行的是子类的方法。
4.覆盖或实现父类的方法时,返回值要变小。
3和4条是为了保证版本升级时的兼容性,比如以前版本是创建了一个father类,新版本创建了子类,并使用子类,满足上述两条后可以实现系统兼容。传递不同的子类,
三、依赖倒置原则(Dependence Inversion Principle, DIP):High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depends upon details. Details should depend upon abstractions.
包含三层含义
1.高层模块不应该依赖底层模块,两者都应该依赖其抽象。
2.抽象不应该依赖于细节
3.细节应该依赖于抽象。
简单的说就是面向接口编程,模块间的依赖是通过抽象发生的,实现类之间不发生直接的依赖关系,其依赖是通过接口或者抽象类发生的。
优点:依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
实现类直接不直接发生依赖关系,降低了耦合性,采用接口编程,由于调用的是接口,不必等实现类完成,就可以对调用者进行开发,测试。
例如在顶层模块实现司机开车功能,司机和车都属于底层。在编写的过程中,要编写司机和车的接口,司机里调用车时,调用的是接口,通过接口,构造函数或者SET方法给接口赋值。当车模型改变时,不需要修改底层的司机类。符合了单一职责,只有司机改变时,才能引起司机类的变化。
依赖的三种实现方法:
构造函数:上例中,建立司机类的时候,通过构造函数给司机类中的车接口赋值。
SET方法:在司机类中,增加一个PUBLIC 的SET方法,调用该方法时,给司机类中的车赋值。
接口方法:在调用的车的方法参数中,有一个参数为车的接口。
编程原则:
1.每个接口尽量有接口或者抽象类,或者抽象类接口都有。
2.变量的表面类型尽量是接口或者抽象类。第1条和第2条不是必须的,比如编写工具类XXXUtils时。
3.尽量不要覆写基类的方法。类间依赖的是抽象,覆写了抽象的方法,会对依赖的稳定性造成一定的影响。
总结:依赖倒置要求编程时,面向抽象或者接口。这就要求子类继承时,尽量不要新增方法,否则按照依赖倒置原则编程,根本访问不到子类新增的方法。
四、接口隔离原则:Client should not be forced to depend upon interfaces that they don’t use. (客户端不应该依赖它不需要的接口。)
The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上)
根据第一种定义,客户端需要什么接口就提供什么接口,把不需要的接口剔掉,这就需要对接口进行细化。
在第二种定义中,要求接口细化。
这两种定义实质上都是要求建立单一接口。
实例:比如星探查找美女,美女的定义是相貌美丽并且气质好。如果在一个接口里定义相貌美丽和气质好会造成接口臃肿,如果美女的标准变了,会对接口造成影响。这里应该使美女实现相貌美丽和气质好两个接口,这样星探的抽象类就依赖两个接口,而不是一个臃肿的接口,灵活性增强。
接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩展,提高系统的灵活性和可维护性。
这个原则的本质相当简单。如果你拥有一个针对多个客户的类,为每一个客户创建特定业务接口,然后使该客户类继承多个特定业务接口将比直接加载客户所需所有方法有效。
图4展示了一个拥有多个客户的类。它通过一个巨大的接口来服务所有的客户。只要针对客户A的方法发生改变,客户B和客户C就会受到影响。因此可能需要进行重新编译和发布。这是一种不幸的做法。
图4 带有集成接口的服务类
我们再看图-5中所展示的技术。每个特定客户所需的方法被置于特定的接口中,这些接口被Service类所继承并实现。
使用接口分离的服务类设计
如果针对客户A的方法发生改变,客户B和客户C并不会受到任何影响,也不需要进行再次编译和重新发布.
概要Clients should not be forced to depend upon interfaces that they do not use.
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
它包含了2层意思:
- 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。
如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。
- 接口的依赖(继承)原则:如果一个接口a依赖(继承)另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应该遵循上述原则:不应该包含用户不使用的方法。
反之,则说明接口a被b给污染了,应该重新设计它们的关系。
如果用户被迫依赖他们不使用的接口,当接口发生改变时,他们也不得不跟着改变。换而言之,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。
接口设计原则
1.接口要尽量小。但是小是有限度的,不能违反单一职责原则,也就不能把一个职责拆分成两个接口。
2.接口要高内聚。高内聚就是提高接口,类和模块的处理能力,减少对外的交互。接口要尽量少的公布public方法,接口是对外的承诺,承诺的越少,对系统开发越有利,变更的风险就越少,同时有利于降低成本。
3.定制服务。定制服务就是单独为一个个体提供优良的服务,我们在做系统设计时,也需要考虑系统之间或模块之间的接口提供定制服务。这就要求接口只提供访问者需要的方法。
4.接口设计是有限度的。接口设计粒度越小,系统越灵活,但是灵活的同时也会带来结构的复杂化,开发难度增加,可维护性降低。
最佳实践
一个接口只服务于一个子模块或者业务逻辑
通过业务逻辑压缩接口中的public方法。
被污染的接口尽量去修改,若修改的风险太大,则可以使用适配器模式进行转化处理,
五、迪米特原则(Law of Demeter, LoD)也成最小知识原则(Least Knowledge Principle ,LKP):一个对象应该对其他对象有最少的了解,通俗的讲,一个类应该对自己需要耦合或调用的类知道得最少。
四层含义:
1.只和朋友交流。迪米特原则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通信)。例如,教师向班长发送命令,数女生的个数。则教师中发送命令方法需要传入班长类,由班长类执行数女生个数。在这中情况下,在教师类中不应该建立女生类,而应该在班长类中建立女生类,对女生个数进行统计。 朋友类:出现在成员变量,或者输入参数的类称为方法类,而出现在方法体内部的类不属于朋友类。因而在上例中,教师类中不应该出现女生类。
2.朋友之间也是有距离的。例如在安装软件时,通过向导类,第一步安装成功可以安装第二步,第二步安装成功,可以执行第三步。在这种情况下,向导类应该对外提供一个安装方法,此方法根据判断条件调用三步安装方法,实现了整个安装过程。而不是让用户去调用每一步安装方法,并根据安装是否成功,决定下一步安装。 在与MES集成的项目中,提供服务的方法类很简单,其主要实现都是调用其他类来实现的。
3.是自己的终究是自己的。在应用中可能出现这样一个方法,放在本类中也可以,放在其他类中叶可以。衡量准则如下:如果一个方法放在本类中,即不增加类间关系,也不会对本类产生负面影响,就放置在本类中。当然,如果该方法有多个类调用,则可放入工具类中。
4.慎用Serializable,如果在项目中,采用远程调用方法传递值对象,该对象就必须实现Serializable接口,也就是对网络传输的对象进行序列号,否则会出现异常。
最佳实践:迪米特法则的核心就是类间解耦,弱耦合,只有解耦后,复用率才可以提高。但是这样会导致产生大量的中转类或者跳转类,导致系统的复杂性提高,同时也给维护带来难度。在实际项目中,一个类跳转两次才能访问到另个一个类,就需要进行重构了。
六、开闭原则 :Software entities like classes, modules and functions should be open for extension but close for modifications. (一个软件实体如类,模块和函数应该对扩展开放,对修改关闭) 开闭原则要求尽量通过扩展软件实体的方法来适应变化,而不是通过修改已有的代码来完成变化。它是为软件实体的未来而制定的对现行开发设计进行约束的一个原则。
简单例子:以图书销售为例,图书有三个属性,价格,书名以及作者。小说书继承了图书接口。如果有一天图小说书打折,修改方案有三种:
(1)修改图书接口,在接口中增加获得打折价格方法。缺点是所有实现图书的接口都需要增加这种方法。
(2)直接修改小说类中获得价格的方法。缺点是:无论谁都看不到小说书的原价。
(3)新写一个打折小说类,继承小说类,覆写其中的价格方法。销售时,将打折小说类赋给图书接口。采购人员查看价格时,可以通过常见小说类实现。
变化分类:我们把变化分为三种。
(1)逻辑变化 只变化一个逻辑,不涉及其它模块。可以通过修改类中的方法来完成。前提条件是所有依赖或者关联的类都按照相同的逻辑处理。
(2)子模块变化 一个子模块变化,会引起高层的变化。因此通过扩展完成变化时,高层次的模块修改也是必然的。
(3)可见视图的变化
如何应用开闭原则:
(1)抽象约束 通过接口或者抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放。包含三层和含义:第一,通过接口和抽象类约束扩展,多扩展进行边界限定,不允许出现在接口或者抽象中不存在的接口。第二,参数类型,引用对象尽量使用接口或者抽象类,而不是实现类。第三,抽象层尽量保持稳定,一旦确定即不允许修改。如果因业务需要增加新的功能,可以写一个继承原有接口的接口。
(2)通过元数据控制模块行为 元数据用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得或者数据库中获得。
(3)封装变化 预测将来可能出现的变化,将相同的变化封装到一个接口或者抽象类中,将不同的变化封装到不同的接口后者抽象类中。
另一篇: