OOD设计基本原则.doc_第1页
OOD设计基本原则.doc_第2页
OOD设计基本原则.doc_第3页
OOD设计基本原则.doc_第4页
OOD设计基本原则.doc_第5页
已阅读5页,还剩1页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

OOD设计基本原则 OCP原则 里氏替换原则 依赖倒置原则 接口隔离原则 聚合与继承原则 单一职责原则 Separation of concerns Principle Pareto Principle (帕雷多原则 80/20原则)OOD设计原则在提高一个系统可维护性的同时,提高这个系统的可复用性.他们是一些指导原则,依照这些原则设计,我们就可以有效的提高系统的复用性,同时提高系统的可维护性.OCP原则 Open-Closed Principle这些OOD原则的一个基石就是开-闭原则(Open-Closed Principle OCP).这个原则最早是由Bertrand Meyer提出,英文的原文是:Software entities should be open for extension, but closed for modification.意思是说,一个软件实体应当对扩展开放,对修改关闭.也就是说,我们在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,换句话说就是,应当可以在不必修改源代码的情况下改变这个模块的行为.满足OCP的设计给系统带来两个无可比拟的优越性. 通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性. 已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性. 具有这两个优点的软件系统是一个高层次上实现了复用的系统,也是一个易于维护的系统.那么,我们如何才能做到这个原则呢?不能修改而可以扩展,这个看起来是自相矛盾的.其实这个是可以做到的,按面向对象的说法,这个就是不允许更改系统的抽象层,而允许扩展的是系统的实现层.解决问题的关键在:抽象化.我们让模块依赖于一个固定的抽象体,这样它就是不可以修改的;同时,通过这个抽象体派生,我们就可以扩展此模块的行为功能.如此,这样设计的程序只通过增加代码来变化而不是通过更改现有代码来变化,前面提到的修改的副作用就没有了.开-闭原则如果从另外一个角度讲述,就是所谓的对可变性封装原则(Principle of Encapsulation of Variation, EVP).讲的是找到一个系统的可变因素,将之封装起来.在我们考虑一个系统的时候,我们不要把关注的焦点放在什么会导致设计发生变化上,而是考虑允许什么发生变化而不让这一变化导致重新设计.也就是说,我们要积极的面对变化,积极的包容变化,而不是逃避. SHALL01将这一思想用一句话总结为:找到一个系统的可变因素,将它封装起来,并将它命名为对可变性的封装原则.对可变性的封装原则意味者两点: 一种可变性应当被封装到一个对象里面,而不应当散落到代码的很多角落里面.同一种可变性的不同表象意味着同一个继承等级结构中的具体子类.继承应当被看做是封装变化的方法,而不应当是被认为从一般的对象生成特殊的对象的方法(继承经常被滥用). 一种可变性不应当与另外一种可变性混合在一起.从具体的类图来看,如果继承结构超过了两层,那么就意味着将两种不同的可变性混合在了一起. 对可变性的封装原则从工程的角度说明了如何实现OCP.如果按照这个原则来设计,那么系统就应当是遵守OCP的.但是现实往往是残酷的,我们不可能100%的遵守OCP,但是我们要向这个目标来靠近.设计者要对设计的模块对何种变化封闭做出选择.里氏替换原则Liskov Substitution Principle从上一篇的开-闭原则中可以看出,面向对象设计的重要原则是创建抽象化,并且从抽象化导出具体化.这个导出要使用继承关系和一个原则:里氏替换原则(Liskov Substitution Principle, LSP). 那么什么是里氏替换原则呢?有个严格的表述,绕口,不好记.还是比较白话的这个好记.说的是:一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它察觉不出基类对象和子类对象的区别.也就是说,在软件里面,把基类都替换成它的子类,程序的行为没有变化. LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为. 下面,我们从代码重构的角度来对LSP进行理解.LSP讲的是基类和子类的关系.只有当这种关系存在时,里氏替换关系才存在.如果两个具体的类A,B之间的关系违反了LSP的设计,(假设是从B到A的继承关系)那么根据具体的情况可以在下面的两种重构方案中选择一种. 创建一个新的抽象类C,作为两个具体类的超类,将A,B的共同行为移动到C中来解决问题.从B到A的继承关系改为委派关系. 为了说明,我们先用第一种方法来看一个例子。第二种办法在另外一个原则中说明.我们就看那个著名的长方形和正方形的例子.对于长方形的类,如果它的长宽相等,那么它就是一个正方形,因此,长方形类的对象中有一些正方形的对象.对于一个正方形的类,它的方法有个setSide和getSide,它不是长方形的子类,和长方形也不会符合LSP. 那么,如果让正方形当做是长方形的子类,会出现什么情况呢?我们让正方形从长方形继承,然后在它的内部设置width等于height,这样,只要width或者height被赋值,那么width和height会被同时赋值,这样就保证了正方形类中,width和height总是相等的.现在我们假设有个客户类,其中有个方法,规则是这样的,测试传人的长方形的宽度是否大于高度,如果满足就停止下来,否则就增加宽度的值.现在我们来看,如果传人的是基类长方形,这个运行的很好.根据LSP,我们把基类替换成它的子类,结果应该也是一样的,但是因为正方形类的width和height会同时赋值,这个方法没有结束的时候,条件总是不满足,也就是说,替换成子类后,程序的行为发生了变化,它不满足LSP. 那么我们用第一种方案进行重构,我们构造一个抽象的四边形类,把长方形和正方形共同的行为放到这个四边形类里面,让长方形和正方形都是它的子类,问题就OK了.对于长方形和正方形,取width和height是它们共同的行为,但是给width和height赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法.上面的例子中那个方法只会适用于不同的子类,LSP也就不会被破坏. 在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承.如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口.当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析.依赖倒置原则 Dependency-Inversion Principles开-闭原则是我们OOD的目标,达到这一目标的主要机制就是依赖倒转原则.这个原则的内容是:要依赖于抽象,不要依赖于具体.对于抽象层次来说,它是一个系统的本质的概括,是系统的商务逻辑和宏观的,战略性的决定,是必然性的体现;具体的层次则是与实现有关的算法和逻辑,一些战术性的决定,带有相当大的偶然性.传统的过程性系统设计办法倾向于使高层次的模块依赖于低层次的模块;抽象层次依赖于具体层次.这实际上就是微观决定宏观,战术决定战略,偶然决定必然.依赖倒转原则就是要把这种错误的依赖关系倒转过来.许多的建构设计模型,例如COM, CORBA, JavaBean, EJB等,它们背后的基本原则就是DIP. 对于软件设计的两个目标,复用和可维护性来说,传统的设计侧重于具体层次模块的复用和可维护,比如算法,数据结构,函数库等等.但是,对系统的抽象是比较稳定的,它的复用是很重要的,同时,抽象层次的可维护性也应当是一个重点.就是说DIP也导致复用和可维护性的倒转. 我们现在来看看依赖有几种,依赖也就是耦合,分为下面三种 零耦合(Nil Coupling)关系,两个类没有依赖关系,那就是零耦合 具体耦合(Concrete Coupling)关系,两个具体的类之间有依赖关系,那么就是具体耦合关系,如果一个具体类直接引用另外一个具体类,就会发生这种关系. 抽象耦合(Abstract Coupling)关系.这种关系发生在一个具体类和一个抽象类之间,这样就使必须发生关系的类之间保持最大的灵活性. DIP要求客户端依赖于抽象耦合,抽象不应当依赖于细节,细节应当依赖于抽象(Abstractions should not depend upon details. Details should depend upon abstractions),这个原则的另外一个表述就是四人团强调的那个:要针对接口编程,不要对实现编程.(Program to an interface, not an implementation),程序在需要引用一个对象时,应当尽可能的使用抽象类型作为变量的静态类型,这就是针对接口编程的含义. DIP是达到开-闭原则的途径.要做到DIP,用抽象方式耦合是关键.由于一个抽象耦合总要涉及具体类从抽象类继承.并且需要保证在任何引用到某类的地方都可以改换成其子类,因此,LSP是DIP的基础.DIP是OOD的核心原则,设计模式的研究和应用都是用它作为指导原则的.DIP虽然强大,但是也很难实现.另外,DIP是假定所有的具体类都会变化,这也不是全对,有些具体类就相当稳定.使用这个类的客户端就完全可以依赖这个具体类而不用再弄一个抽象类.接口隔离原则Interface Segregation Principle接口隔离原则(ISP):使用多个专门的接口比使用单一的总接口要好.也就是说,一个类对另外一个类的依赖性应当是建立在最小的接口上的.这里的接口往往有两种不同的含义: 一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象; 另外一种是指某种语言具体的接口定义,有严 格的定义和结构.比如Java语言里面的Interface结构.对于这两种不同的含义,ISP的表达方式以及含义都有所不同.(上面说的一个类型,可以理解成一个类,我们定义了一个类,也就是定义了一种新的类型)当我们把接口理解成一个类所提供的所有方法的特征集合的时候,这就是一种逻辑上的概念.接口的划分就直接带来类型的划分.这里,我们可以把接口理解成角色,一个接口就只是代表一个角色,每个角色都有它特定的一个接口,这里的这个原则可以叫做角色隔离原则.如果把接口理解成狭义的特定语言的接口,那么ISP表达的意思是说,对不同的客户端,同一个角色提供宽窄不同的接口,也就是定制服务,个性化服务.就是仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来.在我们进行OOD的时候,一个重要的工作就是恰当的划分角色和角色对应的接口.将没有关系的接口合并在一起,是对角色和接口的污染.如果将一些看上去差不多的接口合并,并认为这是一种代码优化,这是错误的.不同的角色应该交给不同的接口,而不能都交给一个接口.对于定制服务,这样做最大的好处就是系统的可维护性.向客户端提供接口是一种承诺,public接口后是不能改变的,因此不必要的承诺就不要做出,承诺越少越好.聚合与继承原则合成(Composition)和聚合(Aggregation)都是关联(Association)的特殊种类。聚合表示整体和部分的关系,表示“拥有”;合成则是一种更强的“拥有”,部分和整体的生命周期一样。合成的新的对象完全支配其组成部分,包括它们的创建和湮灭等。一个合成关系的成分对象是不能与另一个合成关系共享的。换句话说,合成是值的聚合(Aggregation by Value),而一般说的聚合是引用的聚合(Aggregation by Reference)。简短的说,合成聚合复用原则(CARP)是指,尽量使用合成/聚合,而不是使用继承。在OOD中,有两种基本的办法可以实现复用,一种是通过合成/聚合,另外一种就是通过继承。通过合成/聚合的好处是: 新对象存取成分对象的唯一方法是通过成分对象的接口。 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。 这种复用支持包装。 这种复用所需的依赖较少。 每一个新的类可以将焦点集中在一个任务上。 这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。 作为复用手段可以应用到几乎任何环境中去。 它的缺点就是系统中会有较多的对象需要管理。 通过继承来进行复用的优点是: 新的实现较为容易,因为超类的大部分功能可以通过继承的关系自动进入子类。 修改和扩展继承而来的实现较为容易。 缺点是: 继承复用破坏封装,因为继承将超类的实现细节暴露给子类。由于超类的内部细节常常是对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。 如果超类发生改变,那么子类的实现也不得不发生改变。 从超类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。 继承只能在有限的环境中使用。 如何选择?要正确的选择合成/复用和继承,必须透彻的理解里氏替换原则和Coad法则。里氏替换原则前面学习过,Coad法则由Peter Coad提出,总结了一些什么时候使用继承作为复用工具的条件。只有当以下的Coad条件全部被满足时,才应当使用继承关系: 子类是超类的一个特殊种类,而不是超类的一个角色,也就是区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。 永远不会出现需要将子类换成另外一个类的子类的情况。如果不能肯定将来是否会变成另外一个子类的话,就不要使用继承。 子类具有扩展超类的责任,而不是具有置换调(override)或注销掉(Nullify)超类的责任。如果一个子类需要大量的置换掉超类的行为,那么这个类就不应该是这个超类的子类。 只有在分类学角度上有意义时,才可以使用继承。不要从工具类继承。 错误的使用继承而不是合成/聚合的一个常见原因是错误的把“Has-A”当成了“IsA”。“IsA”代表一个类是另外一个类的一种;“Has-A”代表一个类是另外一个类的一个角色,而不是另外一个类的特殊种类。我们看一个例子。如果我们把“人”当成一个类,然后把“雇员”,“经理”,“学生”当成是“人”的子类。这个的错误在于把“角色”的等级结构和“人”的等级结构混淆了。“经理”,“雇员”,“学生”是一个人的角色,一个人可以同时拥有上述角色。如果按继承来设计,那么如果一个人是雇员的话,就不可能是经理,也不可能是学生,这显然不合理。正确的设计是有个抽象类“角色”,“人”可以拥有多个“角色”(聚合),“雇员”,“经理”,“学生”是“角色”的子类。另外一个就是只有两个类满足里氏替换原则的时候,才可能是“IsA”关系。也就是说,如果两个类是“Has-A”关系,但是设计成了继承,那么肯定违反里氏替换原则。单一职责原则Single Responsibility Principle (SRP)就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。 所谓职责,我们可以理解他为功能,就是设计的这个类功能应该只有一个,而不是两个或更多。也可以理解为引用变化的原因,当你发现有两个变化会要求我们修改这个类,那么你就要考虑撤分这个类了。因为职责是变化的一个轴线,当需求变化时,该变化会反映类的职责的变化。“就像一个人身兼数职,而这些事情相互关联不大,甚至有冲突,那他就无法很好的解决这些职责,应该分到不同的人身上去做才对。” 二、举例说明:违反SRP原则代码: modem接口明显具有两个职责:连接管理和数据通讯;interface Modem public void dial(string pno); public void hangup(); public void send(char c); public void recv(); 如果应用程序变化影响连接函数,那么就需要重构:interface DataChannel public void send(char c); public void recv();interface Connection public void dial(string pno); public void hangup(); 三、SRP优点:消除耦合,减小因需求变化引起代码僵化性臭味 四、使用SRP注意点:1、一个合理的类,应该仅有一个引起它变化的原因,即单一职责; 2、在没有变化征兆的情况下应用SRP或其他原则是不明智的; 3、在需求实际发生变化时就应该应用SRP等原则来重构代码; 4、使用测试驱动开发会迫使我们在设计出现臭味之前分离不合理代码; 5、如果测试不能迫使职责分离,僵化性和脆弱性的臭味会变得很强烈,那就应该用Facade或Proxy模式对代码重构;Separation of concerns PrincipleIn computer science, separation of concerns (SoC) is the process of breaking a computer program into distinct features that overlap in functionality as little as possible. A concern is any piece of interest or focus in a program. Typically, concerns are synonymous with features or behaviors. Progress towards SoC is traditionally achieved through modularity and encapsulation, with the help of information hiding. Layered designs in information systems are also often based on separation of concerns (e.g., presentation layer, business logic layer, data access layer, database layer). All programming paradigms aid developers in the process of improving SoC. For example, object-oriented programming languages such as C+, Java, and C# can separate concerns into objects, and a design pattern like MVC can separate content from presentation and data-processing (model) from content. Service-oriented design can separate concerns into services. Procedural programming languages such as C and Pascal can separate concerns into procedures. Aspect-oriented programming languages can separate concerns into aspects and objects. Separation of concerns is an important design principle in many other areas as well, such as urban pla

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论