设计模式六大原则

来源:互联网 发布:dlp数据泄露防护 编辑:程序博客网 时间:2024/05/17 01:04


参考:百度百科


以及:http://blog.csdn.net/GOALSTAR/article/details/3735612


一、OCPOpen close principle


简介


1988年,Bertrand MeyerObject OrientedSoftware Construction》:“Software entities should beopen for extension,but closed for modification”。


翻译过来就是:“软件实体应当对扩展开放,对修改关闭”。这句话说得略微有点专业,我们把它讲得更通俗一点,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。


遵循开闭原则设计出的模块具有两个主要特征:


1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。


2)对于修改是关闭的(Closed formodification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。


实现方法


实现开闭原则的关键就在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。


我们在软件开发的过程中,一直都是提倡需求导向的。这就要求我们在设计的时候,要非常清楚地了解用户需求,判断需求中包含的可能的变化,从而明确在什么情况下使用开闭原则。


关于系统可变的部分,还有一个更具体的对可变性封装原则(Principle of Encapsulation ofVariation, EVP),它从软件工程实现的角度对开闭原则进行了进一步的解释。EVP要求在做系统设计的时候,对系统所有可能发生变化的部分进行评估和分类,每一个可变的因素都单独进行封装。


我们在实际开发过程的设计开始阶段,就要罗列出来系统所有可能的行为,并把这些行为加入到抽象底层,根本就是不可能的,这么去做也是不经济的。因此我们应该现实的接受修改拥抱变化,使我们的代码可以对扩展开发,对修改关闭。


梅耶原则


一个类的实现只应该因错误而修改,新的或者改变的特性应该通过新建不同的类实现。新建的类可以通过继承的方式来重用原类的代码。衍生的子类可以或不可以拥有和原类相同的接口。梅耶的定义提倡实现继承。具体实现可以通过继承方式来重用,但是接口规格不必如此。已存在的实现对于修改是封闭的,但是新的实现不必实现原有的接口。


多态原则


多态开闭原则的定义倡导对抽象基类的继承。接口规约可以通过继承来重用,但是实现不必重用。已存在的接口对于修改是封闭的,并且新的实现必须实现那个接口。


二、LSPLiskov Substitution Principle


简介


里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。


说明


子类型必须能够替换它们的基类型。一个软件实体如果使用的是一个基类,那么当把这个基类替换成继承该基类的子类,程序的行为不会发生任何变化。软件实体察觉不出基类对象和子类对象的区别。


优点


可以很容易的实现同一父类下各个子类的互换,而客户端可以毫不察觉。


如果违反


LSP讲的是基类和子类的关系。只有当这种关系存在时,里氏代换关系才存在。如果两个具体的类AB之间的关系违反了LSP的设计,(假设是从BA的继承关系)那么根据具体的情况可以在下面的两种重构方案中选择一种。


-----创建一个新的抽象类C,作为两个具体类的超类,将AB的共同行为移动到C中来解决问题。


-----BA的继承关系改为委派关系。


三、DIPDependence Inversion Principle


依赖倒置原则


A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。


B.抽象不应该依赖于具体,具体应该依赖于抽象。


所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。


面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。


四、ISPInterface Segregation Principle


使用多个专门的接口比使用单一的总接口要好。


一个类对另外一个类的依赖性应当是建立在最小的接口上的。


一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。


“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。


1、利用委托分离接口。


2、利用多继承分离接口。


五、LODLaw of Demeter)或LKPLeastKnowledge Principle


简介


迪米特法则可以简单说成:talk only to your immediate friends。 对于面向OOD来说,又被解释为下面几种方式:一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。


迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。


迪米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。


注意


迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:


1)在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。


2)在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。


3)在类的设计上,只要有可能,一个类应当设计成不变类。


4)在对其它对象的引用上,一个类对其它对象的引用应该降到最低。


相关描述


狭义的迪米特法则


如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。


广义的迪米特法则在类的设计上的体现:


优先考虑将一个类设置成不变类。


尽量降低一个类的访问权限。


谨慎使用Serializable


尽量降低成员的访问权限。


六、CRPComposite Reuse Principle


说明


如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么尽量使用别的对象提供的功能,使之成为新对象的一部分,而不要自己再重新创建。新对象通过向这些对象的委派达到复用已有功能的。


简而言之,要尽量使用合成/聚合,尽量不要使用继承。


优点:


1)新对象存取成分对象的唯一方法是通过成分对象的接口。


2)这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。


3)这种复用支持包装。


4)这种复用所需的依赖较少。


5)每一个新的类可以将焦点集中在一个任务上。


6)这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。


7)作为复用手段可以应用到几乎任何环境中去。


缺点:


就是系统中会有较多的对象需要管理。


还有个单一职责原则:


 


SRP简介(SRP--Single-Responsibility Principle):


就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。所谓职责,我们可以理解他为功能,就是设计的这个类功能应该只有一个,而不是两个或更多。也可以理解为引用变化的原因,当你发现有两个变化会要求我们修改这个类,那么你就要考虑撤分这个类了。因为职责是变化的一个轴线,当需求变化时,该变化会反映类的职责的变化。


使用SRP注意点


1、一个合理的类,应该仅有一个引起它变化的原因,即单一职责;


2、在没有变化征兆的情况下应用SRP或其他原则是不明智的;


3、在需求实际发生变化时就应该应用SRP等原则来重构代码;


4、使用测试驱动开发会迫使我们在设计出现臭味之前分离不合理代码;


5、如果测试不能迫使职责分离,僵化性和脆弱性的臭味会变得很强烈,那就应该用FacadeProxy模式对代码重构;SRP优点:消除耦合,减小因需求变化引起代码僵化。


 


 


你不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看成警铃,若违背了其中的一条,那么警铃就会响起。


-----Arthur J.Riel


(1)所有数据都应该隐藏在所在的类的内部。


(2)类的使用者必须依赖类的共有接口,但类不能依赖它的使用者。p15


(3)尽量减少类的协议中的消息。


(4)实现所有类都理解的最基本公有接口[例如,拷贝操作(深拷贝和浅拷贝)、相等性判断、正确输出内容、从ASCII描述解析等等] p16


(5)不要把实现细节(例如放置共用代码的私有函数)放到类的公有接口中。


如果类的两个方法有一段公共代码,那么就可以创建一个防止这些公共代码的私有函数。


(6)不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。p17


(7)类之间应该零耦合,或者只有导出耦合关系。也即,一个类要么同另一个类毫无关系,要么只使用另一个类的公有接口中的操作。 p18


(8)类应该只表示一个关键抽象。


包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包影响,则将对包中的所有类产生影响,而对其他的包不造成任何影响 .


(9)把相关的数据和行为集中放置。


设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。这种类型的行为暗示着这条经验原则被违反了。


(10)把不相关的信息放在另一个类中(也即:互不沟通的行为)p19


朝着稳定的方向进行依赖.


(11)确保你为之建模的抽象概念是类,而不只是对象扮演的角色。p23


(12)在水平方向上尽可能统一地分布系统功能,也即:按照设计,顶层类应当统一地共享工作。


(13)在你的系统中不要创建全能类/对象。对名字包含DriverManagerSystemSusystem的类要特别多加小心。


规划一个接口而不是实现一个接口。


(14)对公共接口中定义了大量访问方法的类多加小心。大量访问方法意味着相关数据和行为没有集中存放。


(15)对包含太多互不沟通的行为的类多加小心。


这个问题的另一表现是在你的应用程序中的类的公有接口中创建了很多的getset函数。


(16)在由同用户界面交互的面向对象模型构成的应用程序中,模型不应该依赖于界面,界面则应当依赖于模型。


(17)尽可能地按照现实世界建模(我们常常为了遵守系统功能分布原则、避免全能类原则以及集中放置相关数据和行为的原则而违背这条原则)


(18)从你的设计中去除不需要的类。


一般来说,我们会把这个类降级成一个属性。


(19)去除系统外的类。


系统外的类的特点是,抽象地看它们只往系统领域发送消息但并不接受系统领域内其他类发出的消息。


(20)不要把操作变成类。质疑任何名字是动词或者派生自动词的类,特别是只有一个有意义行为的类。考虑一下那个有意义的行为是否应当迁移到已经存在或者尚未发现的某个类中。


(21)我们在创建应用程序的分析模型时常常引入代理类。在设计阶段,我们常会发现很多代理没有用的,应当去除。


(22)尽量减少类的协作者的数量。


一个类用到的其他类的数目应当尽量少。


(23)尽量减少类和协作者之间传递的消息的数量。


(24)尽量减少类和协作者之间的协作量,也即:减少类和协作者之间传递的不同消息的数量。


(25)尽量减少类的扇出,也即:减少类定义的消息数和发送的消息数的乘积


(26)如果类包含另一个类的对象,那么包含类应当给被包含的对象发送消息。也即:包含关系总是意味着使用关系。


(27)类中定义的大多数方法都应当在大多数时间里使用大多数数据成员。


 


 


(28)类包含的对象数目不应当超过开发者短期记忆的容量。这个数目常常是6


当类包含多于6个数据成员时,可以把逻辑相关的数据成员划分为一组,然后用一个新的包含类去包含这一组成员。


(29)让系统功能在窄而深的继承体系中垂直分布。


(30)在实现语义约束时,最好根据类定义来实现。这常常会导致类泛滥成灾,在这种情况下,约束应当在类的行为中实现,通常是在构造函数中实现,但不是必须如此。


(31)在类的构造函数中实现语义约束时,把约束测试放在构造函数领域所允许的尽量深的包含层次中。


(32)约束所依赖的语义信息如果经常改变,那么最好放在一个集中式的第3方对象中


(33)约束所依赖的语义信息如果很少改变,那么最好分布在约束所涉及的各个类中。


(34)类必须知道它包含什么,但是不能知道谁包含它。


(35)共享字面范围(也就是被同一个类所包含)的对象相互之间不应当有使用关系


(36)继承只应被用来为特化层次结构建模。


(37)派生类必须知道基类,基类不应该知道关于它们的派生类的任何信息。


(38)基类中的所有数据都应当是私有的,不要使用保护数据。


 


类的设计者永远都不应该把类的使用者不需要的东西放在公有接口中。


(39)在理论上,继承层次体系应当深一点,越深越好。


(40)在实践中,继承层次体系的深度不应当超出一个普通人的短期记忆能力。一个广为接受的深度值是6


(41)所有的抽象类都应当是基类


(42)所有的基类都应当是抽象类


(43)把数据、行为和/或接口的共性尽可能地放到继承层次体系的高端。


(44)如果两个或更多个类共享公共数据(但没有公共行为),那么应当把公共数据放在一个类中,每个共享这个数据的类都包含这个类。


(45)如果两个或更多个类有共同的数据和行为(就是方法),那么这些类的每一个都应当从一个表示了这些数据和方法的公共基类继承。 (46)如果两个或更多个类共享公共接口(指的是消息,而不是方法),那么只有他们需要被多态地使用时,他们才应当从一个公共基类继承。 (47)对对象类型的显示的分情况分析一般是错误的。在大多数这样的情况下,设计者应当使用多态。


(48)对属性值的显示的分情况分析常常是错误的。类应当解耦合成一个继承层次结构,每个属性值都被变换成一个派生类。


 


 


(49)不要通过继承关系来为类的动态语义建模。试图用静态语义关系来为动态语义建模会导致在运行时切换类型。


(50)不要把类的对象变成派生类。对任何只有一个实例的派生类都要多加小心。


(51)如果你觉得需要在运行时刻创建新的类,那么退后一步以认清你要创建的是对象。现在,把这些对象概括成一个类。


(52)在派生类中用空方法(也就是什么也不做的方法)来覆写基类中的方法应当是非法的。


(53)不要把可选包含同对继承的需要相混淆。把可选包含建模成继承会带来泛滥成灾的类。


(54)在创建继承层次时,试着创建可复用的框架,而不是可复用的组件。


(55)如果你在设计中使用了多重继承,先假设你犯了错误。如果没犯错误,你需要设法证明。


(56)只要在面向对象设计中用到了继承,问自己两个问题:(1)派生类是否是它继承的那个东西的一个特殊类型?(2)基类是不是派生类的一部分?


(57)如果你在一个面向对象设计中发现了多重继承关系,确保没有哪个基类实际上是另一个基类的派生类。


(58)在面向对象设计中如果你需要在包含关系和关联关系间作出选择,请选择包含关系。


(59)不要把全局数据或全局函数用于类的对象的薄记工作。应当使用类变量或类方法。


(60)面向对象设计者不应当让物理设计准则来破坏他们的逻辑设计。但是,在对逻辑设计作出决策的过程中我们经常用到物理设计准则。


(61)不要绕开公共接口去修改对象的状态。



0 0
原创粉丝点击