我们在应用开发中,一般要求尽量做到可维护性和可复用性
应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性。而在面向对象的设计里面,可维护性和复用都是以面向对象设计原则为基础的,这些设计原则首页都是复用原则,遵循这些设计原则可以有效的提高系统的复用性,同时提高系统的可维护性。面向对象设计原则和设计模式也是对系统进行合理重构的指导方针。
常用的面向对象设计原则包括7个,这些原则 并不是孤立存在的,他们相互依赖,相互补充。
设计原则:
单一职责原则
类的职责要单一,不能将太多的职责放在一个类中开闭原则
<p>软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能里氏代换原则
<p>在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象依赖倒转原则
要针对抽象层编程,而不要针对具体类编程接口隔离原则
使用多个专门接口来取代一个统一的接口合成复用原则
在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系迪米特法则
一个软件实体对其它实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互
一、单一职责原则:类的职责要单一,不能将太多的职责放在一个类中。(高内聚、低耦合)
定义:一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中。
- 原则分析
- 一个类(或者大道模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响到其它的职责运作。
- 类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性体现,而行为职责通过其方法体现。
- 单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到他的存在,它是最简单但又是最难运用的原则,需要设计人员发现类的不同职责将其分离,而发现的类多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
- 优点
- 降低类的复杂性,类的职责清晰明确。比如数据职责和行为职责清晰明确
- 提高可读性和维护性
- 变更引起的风险减低,变更时必不可少的,如果接口的单一职责做得好,一个接口修改,只对相应的类有影响,对其它接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
注意:单一职责原则提出了一个编写程序的标准,用职责或变化原因来衡量接口或类设计得是否合理,但是职责和变化原因都是没有具体标准的,一个类到底负责哪些职责?这些职责怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的情况考虑。因项目而异,因环境而异。
二、开闭原则:对扩展开放,对修改关闭(设计模式的核心原则)
定义:一个软件实体(如类、模块和函数)应该对扩展开放,对修改关闭。意思是,在一个系统或者模块中,对扩展是开发的,对修改是关闭的,一个好的系统是在不修改源代码的情况下,可以扩展你的功能。而实现开闭原则的关健就是抽象化
- 原则分析
- 当软件实体因需求要变化时,尽量通过扩展已有的软件实体,可以提供新的行为,以满足对软件的新的需求,而不是修改已有的代码,使变化中软件有一定的适应性和灵活性。已有软件模块,特别是最重要的抽象层模块不能修改,这使得变化中的软件系统有一定的稳定性和延续性。
- 实现开闭原则的关健是抽象化:在“开-闭”原则中,不允许修改的是抽象的类或者接口,允许扩展的是具体的实体类,抽象类和接口在“开-闭”原则中扮演着极其重要的角色。即要预见可能变化的需求,又要预见所有可能已知的扩展。所以在这抽象化是关健
- 可变性的封闭原则:找到系统的可变因素,将他封装起来。这是对“开-闭”原则最好的实现,不要把你的可变因素放在多个类中,或者散落在程序的各个角落。你应该将可变因素封套起来。并且切忌不要把所有的可变因素封套在一起。最好的解决办法是,分块封套你的可变因素。避免超大类,超长类,超长方法的出现。给你程序增加艺术气息,将程序艺术化是我们的目标!
三、里氏代换原则:任何基类可以出现的地方,子类也可以出现
定义:第一种定义方式相对严格:如果对每一个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所在的对象O1都代换成O2时,程序P的行为没有变化,那么类型S是类型T的子类。
第二种容易理解的定义方式:所有引用基类(父类)的地方必须能透明的使用其子类的对象。即子类能够替换基类出现的地方。子类也能够在基类的基础上新增行为。
原则分析
讲的是基类和子类的关系,只有这种关系存在时,里式代换原则才存在。正方形是长方形是理解里式代换原则的经典例子
里氏代换原则可以通俗的表述为:在软件中如果能够使用基类对象,那么一定能够使用子类对象。把基类都替换为子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能都使用基类。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定子类类型,用子类对象来替换父类对象。
优点
代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
提高代码的重用性
子类可以形似父类,但又异于父类
提高代码的可扩展性,实现父类的方法就可以“为所欲为”了
提高产品或项目的开发性
缺点
继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法
降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束
增强了耦合性。当父类常量、变量和方法被修改,必须考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果,大片代码需要重构
四、依赖倒转原则:要依赖抽象,而不要依赖具体的实现
定义:高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。依赖倒转原则要求客户端依赖于抽象耦合。
- 原则表述:
- 抽象不应当依赖于细节;细节应当依赖于抽象;
- 要针对接口编程,不针对实现编程。
- 原则分析
- 如果说开闭原则是面向对象设计的目标,依赖倒转原则是到达面向设计开闭原则的手段,如果要达到最好的开闭原则,就要尽量遵守依赖倒转原则,可以说依赖倒转原则对抽象的最好规范! 其实,依赖倒转原则也是里氏代换原则的补充,理解了里氏代换原则,再来理解依赖倒转原则应该很容易的。
- 依赖倒转原则的常用方式之一是在代码中使用抽象类,二将具体类放在配置文件中。
- 类之间的耦合:零耦合关系,抽象耦合关系。依赖倒转原则要求客户端依赖于抽象耦合,一抽象方式耦合是依赖倒转原则的关键
理解这个依赖倒置,首先我们需要明白依赖在面向对象设计的概念:
依赖关系:是一种使用关系,特定事务的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。大多数情况系,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
- 优点
采用依赖倒转原则减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本就直接依赖笔记本,而编写程序需要的是对现实世界的事物进行抽象,抽象的结构就是有了抽象类和接口,然后我们根据系统设计的需求产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖,倒置就是从这里产生的。
五、合成/聚合复用原则:要尽量使用对象组合,而不是继承关系达到软件复用的目的
定义:经常又叫做合成复用原则,尽量使用对象组合而不是继承来达到复用的目的。就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。简而言之,要尽量使用合成/聚合,尽量不要使用继承
。
原则分析
1)在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承关系。
继承复用:实现简单,易于扩展。破话系统的封装性;从基类继承而来的实现是静态的。不可能在运行时发生改变,没有足够的灵活性;只有在有限的环境中使用 白箱复用
组合/聚合复用:耦合度相对较低,选择性的调用成员对象的操作;可以在运行时动态进行。黑箱复用
2)组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较小,因此一般首先选择使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则。有效的使用继承会有助于对问题的理解,较低复杂度。而乱用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
3)此原则与里氏代换原则相辅相成,两者都是事先开-闭原则的规范。违反这一原则,就无法实现开-闭原则。-
首先我们要明白合成和聚合的概念:
- 什么是组合?
合成(组合):表示一个整体与部分的关系,指一个依托整体而存在的关系(整体与部分不可以分开),例如:一个人对他的房子和家具,其中他的房子和家具是不能被共享的,因为那些东西都是他自己的。并且人没了,这个也关系就没了。这个例子就好像,乌鸡百凤丸这个产品,它是有乌鸡和上等药材合成而来的一样。也比如网络游戏中的武器装备合成一样,多种东西合并为一种超强的东西一样。
虽然组合表示的是一个整体与部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有同生共死的关系。
在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。 - 什么是聚合?
聚合:聚合是比合成关系的一种更强的依赖关系,也表示整体与部分的关系(整体与部分可以分开),例如,一个奔驰S360汽车,对奔驰S360引擎,奔驰S360轮胎的关系..这些关系就是带有聚合性质的。因为奔驰S360引擎和奔驰S360轮胎他们只能被奔驰S360汽车所用,离开了奔驰S360汽车,它们就失去了存在的意义。在我们的设计中,这样的关系不应该频繁出现.这样会增大设计的耦合度。
在面向对象中的聚合:通常在定义一个整体类后,再去分析这个整体类的组成结构,从而找出一些成员类,该整体类和成员类之间就形成了聚合关系。在聚合关系中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。
- 什么是组合?
明白了合成和聚合关系,再来理解合成/聚合原则应该就清楚了。要避免在系统设计中出现,一个类的继承层次超过3次。如果这样的话,可以考虑重构你的代码,或者重新设计结构. 当然最好的办法就是考虑使用合成/聚合原则。
六、迪米特法则:系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度
定义:又叫最少知识原则(Least Knowledge Principle或简写为LKP)\几种形式定义: (1) 不要和“陌生人”说话。英文定义为:Don't talk to strangers. Ÿ(2) 只与你的直接朋友通信。英文定义为:Talk only to your immediatefriends. (3) 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。 简单地说,也就是,一个对象应当对其它对象有尽可能少的了解。一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。
- 原则分析:
1)朋友类:
在迪米特法则中,对于一个对象,其朋友包括以下几类:
Ÿ(1) 当前对象本身(this);
Ÿ(2) 以参数形式传入到当前对象方法中的对象;
Ÿ(3) 当前对象的成员对象;
Ÿ(4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
Ÿ(5) 当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
2)狭义法则和广义法则:
在狭义的迪米特法则中,如果两个类之间不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
狭义的迪米特法则:可以降低类之间的耦合,但是会在系统中增加大量的小方法并散落在系统的各个角落,它可以使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联,但是也会造成系统的不同模块之间的通信效率降低,使得系统的不同模块之间不容易协调。
广义的迪米特法则:指对对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。信息的隐藏可以使各个子系统之间脱耦,从而允许它们独立地被开发、优化、使用和修改,同时可以促进软件的复用,由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越重要,而信息隐藏的重要性也就越明显。
3)迪米特法则的主要用途:在于控制信息的过载。
•在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;
•在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;
•在类的设计上,只要有可能,一个类型应当设计成不变类;
•在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
例子:外观模式
系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度,因为在你的系统中,扩展的时候,你可能需要修改这些类,而类与类之间的关系,决定了修改的复杂度,相互作用越多,则修改难度就越大,反之,如果相互作用的越小,则修改起来的难度就越小..例如A类依赖B类,则B类依赖C类,当你在修改A类的时候,你要考虑B类是否会受到影响,而B类的影响是否又会影响到C类. 如果此时C类再依赖D类的话,呵呵,我想这样的修改有的受了。
七、接口隔离原则:客户端不应该依赖哪些它不需要的接口。(这个法则与迪米特法则是相通的)
定义:客户端不应该依赖那些它不需要的接口。另一种定义方法:一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。 注意,在该定义中的接口指的是所定义的方法。例如外面调用某个类的public方法。这个方法对外就是接口。
- 原则分析:
1)接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
• (1)一个接口就只代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫做“角色隔离原则”。
• (2)接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
2)使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
3)可以在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为。
下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口(胖接口)AbstractService来服务所有的客户类。可以使用接口隔离原则对其进行重构。
迪米特法则是目的,而接口隔离法则是对迪米特法则的规范. 为了做到尽可能小的耦合性,我们需要使用接口来规范类,用接口来约束类.要达到迪米特法则的要求,最好就是实现接口隔离法则,实现接口隔离法则,你也就满足了迪米特法则。
内容转载
http://blog.csdn.net/husen1314/article/details/42060667
http://blog.csdn.net/hguisu/article/details/7571617