• 42.50 KB
  • 15页

面向对象设计基本原则

  • 15页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档共5页,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,可选择认领,认领后既往收益都归您。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细先通过免费阅读内容等途径辨别内容交易风险。如存在严重挂羊头卖狗肉之情形,可联系本站下载客服投诉处理。
  4. 文档侵权举报电话:19940600175。
'面向对象设计基本原则面向对象设计基本原则1、单一职责原则(SingleResponcePrinciple)SRP定义:对一个类或接口而言,应仅有一个引起它变化的原因。在SRP中,我们把职责定义为“变化的原因”(areasonforchange)。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多个职责。本原则基本途径就是提高内聚性,如果一个类承担的职责过多,那么这些职责就相互依赖,一个职责的变化可能会影响另一个职责的履行。其实OOD的实质,就是合理的进行类的职责分配。对于何时遵循SRP有以下考虑:1、如果应用程序的变化会影响到类中某一种职责,那么就应该将它与另一种职责分离,这样做可以避免客户应用程序和类中的职责耦合在一起。2、如果应用程序的变化总是会导致两个职责的同时变化,那么就不必分离它们。实际上,分离它们会带来不必要的复杂性。从上可得知:变化的轴线仅当变化实际发生时才具有真正的意义。如果没有征兆,那么去应用SRP或其他任何原则都是不明智的。--------------------------------------------------------------------------------2、开放-封闭原则(TheOpen-ClosedPrinciple)OCP:一个软件实体(类、模块、函数等),应该是可扩展的,但是不可修改的。[SoftwareEntitiesShouldBeOpenForExtension,YetCloseForModification]该原则认为我们应该试图设计出永远也不需要改变的模块。这是理想状态,但我们应该努力最小化不满足OCP原则的模块数量。开放--封闭原则是OOD的真正核心。对扩展开放,模板行为可以被扩展,以满足新的需求;对更改封闭,模块的源代码是不允许改动的。如何做到这两点呢?关键在于抽象;把可能的变化用抽象隔离之。 符合此原则意味着最高等级的复用性(Reusability)和可维护性(Maintainability)。--------------------------------------------------------------------------------3、里氏替换原则(LiskovSubstitutionPrinciple)LSP描述:使用指向基类(超类)的引用的函数(方法),必须能够在不知道具体派生类(子类)对象类型的情况下使用它们。[FunctionTharUseReferenncesToBase(Super)ClassesMustBeAbleToUseObjectsOfDerived(Sub)ClassesWithoutKnowingIt]LSP严格的表达是:如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得T1定义的所有程序P在所有的对象O1替换成O2的时候,程序的行为没有发生变化,那么类型T2是类型T1的子类型。LSP是继承复用的基石;它清楚的表示了ISA关系全部都是与行为有关的;所有子类必须符合使用基类的Client所期望的行为;子类型也不得具有比基类更多的限制。LSP原则要求父类应尽可能使用接口或抽象类来实现,即面向接口编程。--------------------------------------------------------------------------------4、依赖倒置原则(DependencyInversionPrinciple)DIP描述:高层模块不依赖于低层模块,两者都依赖抽象;抽象不应依赖于细节,细节应依赖于抽象。 此原则并非普适于任何上下文,它要求我们把接口(或抽象类)放在使用方(高层),而对被使用方(实现方、低层)透明。依赖真的要倒置么取决于上下文,接口归属权看谁是系统的核心。--------------------------------------------------------------------------------5、接口隔离原则(InterfaceSegregationPrinciple)ISP描述:从一个客户类的角度来讲,一个类对另外一个类的依赖性应当是建立在最小的接口上的。使用多个专门的接口比使用单一的总接口要好。一个没有经验的设计师往往想节省接口的数目,将一些功能相关或相近的接口合并,造成接口污染。面向对象设计的原则2008-08-2016:551.类的设计原则在网上,一般认为灯的设计原则有五类,如下:1)SRP,单一职责原则,一个类应该有且只有一个改变的理由。所谓单一职责原则,就是就一个类而言,应该仅有一个引起它的变化的原因。换句话说,一个类的功能要单一,只做与它相关的事情。  这个原则是最简单、最容易理解,却是最不容易做到的事情。这个原则的道理谁都理解,可是在实践中呢?   我们来看一个例子:if(action.equals("load")&&tab.equals("1")){request.setAttribute("tabId",tab);form.set("tabId",tab);speciManager.loadIncrement(actionForm,request,tab);}if(action.equals("Save")&&tab.equals("1")){System.out.println("interincrementsaveaction");……request.setAttribute("tabId",tab);}if(action.equals("load")&&tab.equals("2")){request.setAttribute("tabId",tab);form.set("tabId",tab);speciManager.loadMeasureMent(actionForm,request,tab);}if(action.equals("Save")&&tab.equals("2")){……System.out.println("interincrementsaveaction");speciManager.loadIncrement(actionForm,request,tab);form.set("tabId",tab);request.setAttribute("tabId",tab);}  一看就知道这个类做了太多的工作,它既要load一个tab为1的页面和一个tab为2的页面;又要save一个tab为1页面和一个tab为2的页面。这个类的代码我只截取了里面很少的一部分,绝大部分的代码我都省略掉了。这段代码写到最后是越来越混乱,直到最后失败。   对照着这个例子,我们再来分析一下为什么要遵守单一职责愿则:  第一、有助于我们分析和编码的思路的清晰。当你的代码里有了三层或以上的if语句或for语句的嵌套的时候,你不要跟我说,你已经把问题分析得很清楚了。多层嵌套的if或for语句只能说明你还没有把问题分析清楚。  第二、使我们的编码、测试和维护变得简单。  第三、将一个个复杂的问题简单化以后,易于代码的重用。当你的代码的多个功能搅和在一起的时候,你是没办法考虑代码的重用的,因为你的每一处代码都有不同。  第四、易于系统的扩展。2)OCP,开放封闭原则,你应该能够不用修改原有类就能扩展一个类的行为。“开一闭”原则讲的是:一个软件实体应当对扩展开放,对修改关闭。这个规则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。从另外一个角度讲,就是所谓的“对可变性封装原则”。“对可变性封装原则”意味着两点:1.一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级结构中的具体子类。2.一种可变性不应当与另一种可变性混合在一起。即类图的继承结构一般不应超过两层。做到“开—闭”原则不是一件容易的事,但是也有很多规律可循,这些规律同样也是设计原则,它们是实现开—闭原则的工具3)LSP,Liskov替换原则,派生类要与其基类自相容。里氏代换原则:就是子类代替父类,程序或者代码的行为不变。例如如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都换成o2时,程序P的行为没有变化,那么类型T2是T1的子类型。 即如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。如果有两个具体类A和B之间的关系违反了里氏代换原则,可以在以下两种重构方案中选择一种:1创建一个新的抽象类C,作为两个具体类的超类,将A和B共同的行为移动到C中,从而解决A和B行为不完全一致的问题。2从B到A的继承关系改写为委派关系。4)DIP,依赖倒置原则,依赖于抽象而不是实现。依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体。即针对接口编程,不要针对实现编程。针对接口编程的意思是,应当使用接口和抽象类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明、参量的类型声明,方法的返还类型声明,以及数据类型的转换等。依赖倒转原则虽然强大,但却不易实现,因为依赖倒转的缘故,对象的创建很可能要使用对象工厂,以避免对具体类的直接引用,此原则的使用还会导致大量的类。维护这样的系统需要较好的面向对象的设计知识。此外,依赖倒转原则假定所有的具体类都是变化的,这也不总是正确的。有一些具体类可能是相当稳定、不会发生变化的,消费这个具体类实例的客户端完全可以依赖于这个具体类。5)ISP,接口隔离原则,客户只要关注它们所需的接口。接口隔离原则讲的是:使用多个专门的接口比使用单一的接口要好。从客户的角度来说:一个类对另外一个类的依赖性应当是建立在最小的接口上的。如果客户端只需要某一些方法的话,那么就应当向客户端提供这些需要的方法,而不要提供不需要的方法。提供接口意味着向客户端作出承诺,过多的承诺会给系统的维护造成不必要的负担。2.包原则REP,重用发布等价原则,重用的粒度就是发布的粒度。 CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。CRP,共同重用原则,一个包中的所有类应该是共同重用的。3.包之间的耦合性原则ADP,无环依赖原则,在包的依赖关系图中不允许存在环。SDP,稳定依赖原则,朝着稳定的方向进行依赖。SAP,稳定抽象原则,包的抽象程度应该和其稳定程度一致。1晨后综合症和包的发布你曾有过这样的经历吗?工作了一整天,终于完成了某项功能后回家,不料第二天早晨一来却发现那项功能不再工作了。原因是什么呢?因为有人比你走得更晚,并且更改了你所依赖的某些东西!这就是所谓的“晨后综合症"。受“晨后综合症”困扰的团队常常几周时间都无法构建出一个稳定的版本,每个人都忙于一遍遍更改他们的代码,试图使之能够相容与其他人所做的最近更改,从而造成团队士气低落,效率不高,陷入了可怕的集成地狱中。为此有的团队在集成期间禁止checkin,这显然不是一个好办法,一方面,如果不用技术手段很难避免有意或无意的checkin,另一方面开发者为了进行后续开发而又不影响当前集成的版本,可能不得不手工进行版本管理,再有,它使团队成员间的协作也变得困难。对包进行发布能有效避免晨后综合症的发生。造成晨后综合症的原因在于依赖者所依赖的包是变化的,这就使依赖者工作于一个不稳定的基础之上,对被依赖者的改变,依赖者必须作出相容的改变。采用包的发布机制,依赖者必须选择被依赖包发布的一个特定版本,而发布后的包,其内容是不允许变化的,因此依赖者所依赖的东西就不会改变;同时,被依赖包的任何改变都必须作为一个新版本发布,而依赖者有权决定是否采用这个新版本,换句话说,是否接受被依赖者的改变是由依赖者决定的,而在此之前,依赖者是不得不被动地接受被依赖者的改变。 2包的设计原则要对包进行发布,首先要先设计好包,对规模较大的应用来说,划分包的组合很多,仅仅把看起来像是适合在一起的类放进相同的包中,得到的往往是一种不好的包结构:发布很困难、不容易重用、难于更改等等,这种包结构带来的可能是更多的麻烦。显然我们需要一些原则来指导包的划分,以下列出这些原则,前三个原则用来指导把类划分到包中,关注包的内聚性,后三个原则用来处理包之间的相互关系,关注包的耦合性。2.1REP重用发布等价原则重用的粒度就是发布的粒度。如果一个包中的软件是用来重用的,那么它就不能再包含不是为了重用目的而设计的软件。换句话说,一个包中的软件要么都是可重用的,要么都不是可重用的。简单地声明一个类是可重用的做法是不现实的,我们所重用的任何东西都必须同时被发布和跟踪。如果一个包同时包含了可重用的类和不可重用的类,那么当不可重用的类发生变化时,就要进行一次包的发布,而原本不受影响的重用者就需要决定是否采用新版本,以及采用新版本后可能的编译,连接和测试工作。这些内耗操作是应该避免的。2.2CRP共同重用原则一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。在大多数情况下,一个可重用抽象需要多个类来表达,该原则规定这些类应该在一个包中,而属于不同抽象的类不应该在一个包中。乍看起来,这条原则和REP有点相似,但实际上还是不同的,比如,抽象A包括类A1、A2、A3,抽象B包含类B1、B2、B3,从REP 原则来看,这个包没有什么不对,因为该包的软件都是可重用的,但它却违反了CRP原则,因为重用者完全可以只重用A抽象的类或只重用B抽象的类,这样当任一抽象的改变都会导致该包重新发布,虽然该发布对某一抽象的使用者是无意义的,但是他们仍然需要需要重新验证和重新发布,这会白费相当数量的努力。2.3CCP共同封闭原则包中的所用类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对其他的包不造成任何影响。REP和CRP关注的都是重用性,CCP关注的是可维护性。对大多数的应用来说,可维护性的重要性是超过可重用性的。一个变化(包括需求上的,设计上的等等)可能会引起多个类的更改,CCP要求我们把这些类放在一个包中,同时把那些不受影响的类放到其他的包中,因此一个包只有一个引起变化的原因,一个变化只对一个包产生影响,这样大大减少了重新发布的次数和重新发布对其他包的影响,从而提高了软件的可维护性。2.4ADP无环依赖原则在包的依赖关系图中不允许存在环。如果包的依赖关系图中存在环,就会导致环上的所有软件包必须同时发布,如下图,由于P3使用了P1中的一个类而形成了依赖环,若要发布P1,必须先发布P2和P3,而发布P3又要先发布P1和P2,P1、P2、P3实际上已经变成了同一个大包,于是工作于这些包上的开发人员不可避免地会遭受晨后综合症。去掉依赖环的方法有两个:使用DIP。如图,把Y的接口和实现分离, 增加新包。如图,把P1和P3都依赖的类移到一个新包中2.5SDP稳定依赖原则朝着稳定的方向进行依赖。如果一个包被很多包所依赖,那么它就是稳定的,因为要使所有依赖于它的包能够相容于对它所做的更改,往往需要非常大的工作量。一个系统中的所有包并非都应该都是稳定的,因为如果那样的话,系统将很难更改。SDP指导我们处理稳定包和不稳定包之间的关系:不稳定的包应该依赖于稳定的包,一个包应该依赖于比他更稳定的包。你设计了一个不稳定的包,期望它能随变化容易地更改,可当它被一个稳定的包依赖后,它就再也不会易于更改了,这就使软件难于修改和变化。2.6SAP稳定抽象原则包的稳定程度应该和其稳定程度一致。一个系统的高层构架和设计决策应该被放进稳定的包中,因为这些构架决策不应该经常改变,然而稳定包的不易更改的特点会使这些架构决策不灵活,显然只用稳定性来度量一个包是不够的,SAP告诉我们:稳定的包也应该是抽象的。它应该包含抽象类,系统通过在其他包中实现该稳定包中的抽象类来进行扩展,而该稳定包无需修改,从而在保持稳定的同时也不失灵活性。3包的设计过程 几年前当我还不知道这些包的设计原则时,认为包就是系统的高层的功能分解,应该在项目开始时就设计好,结果遭受了失败。实际上,包的依赖关系图和描绘系统的功能之间几乎没有关系,它是系统可构建性的映射图。项目开始时,我们还不知道系统中有哪些类,更不必说它们之间的关系,在这种情况下不仅很难创建出包依赖关系图,即使创建出来也很可能是不合理的。包的依赖关系结构应该是和系统的逻辑设计一起增长和演化的,项目开始时不必考虑包,系统仍以类的粒度组织,随着开发的进行,类越来越多,对依赖关系进行管理,避免项目开发中出现晨后综合症的需要也不断增长,此时我们应用CCP原则对把可能一同变化的类组织成包进行发布,随着系统的不断增长,我们开始关注创建可重用的元素,于是开始使用CRP和REP来指导包的组合。最后使用ADP、SDP、SAP对包图进行度量,去掉不好的依赖。4你真的采用了包的发布机制了吗如果说某个做产品的团队没有采用包的发布机制,它也许会大叫冤枉:我们明明是进行了发布的呀,你看,这不是我们发布的1.0、1.2版本吗?事实上,任何一个产品都离不开包的发布,只不过对这样的团队而言,他们系统里真正意义上的包只有一个,那就是整个系统,而该包的发布常常是在经过了晨后综合症的洗礼之后,是在开发基本完毕后进行的,实际上,我们更应该在产品的开发过程中使用这一机制,这样,产品的开发过程才会以合理,有序的方式进行,产品才能尽快地交付。界面进入GUI时代。 面向对象的基本概念(1)对象。对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。(2)对象的状态和行为。对象具有状态,一个对象用数据值来描述它的状态。对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中(3)类。具有相同或相似性质的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象。类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。(4)类的结构。在客观世界中有若干类,这些类之间有一定的结构关系。通常有两种主要的结构关系,即一般--具体结构关系,整体--部分结构关系。①一般——具体结构称为分类结构,也可以说是“或”关系,或者是“isa”关系。 ②整体——部分结构称为组装结构,它们之间的关系是一种“与”关系,或者是“hasa”关系。(5)消息和方法。对象之间进行通信的结构叫做消息。在对象的操作中,当一个消息发送给某个对象时,消息包含接收对象去执行某种操作的信息。发送一条消息至少要包括说明接受消息的对象名、发送给该对象的消息名(即对象名、方法名)。一般还要对参数加以说明,参数可以是认识该消息的对象所知道的变量名,或者是所有对象都知道的全局变量名。类中操作的实现过程叫做方法,一个方法有方法名、参数、方法体面向对象的特征(1)对象唯一性。每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变,不同的对象不能有相同的标识。(2)分类性。分类性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。(3)继承性。继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。 继承性是面向对象程序设计语言不同于其它语言的最重要的特点,是其他语言所没有的。在类层次中,子类只继承一个父类的数据结构和方法,则称为单重继承。在类层次中,子类继承了多个父类的数据结构和方法,则称为多重继承。在软件开发中,类的继承性使所建立的软件具有开放性、可扩充性,这是信息组织与分类的行之有效的方法,它简化了对象、类的创建工作量,增加了代码的可重性。采用继承性,提供了类的规范的等级结构。通过类的继承关系,使公共的特性能够共享,提高了软件的重用性。(4)多态性(多形性)多态性使指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。多态性增强了软件的灵活性和重用性。三、面向对象的要素(1)抽象。抽象是指强调实体的本质、内在的属性。在系统开发中,抽象指的是在决定如何实现对象之前的对象的意义和行为。使用抽象可以尽可能避免过早考虑一些细节。类实现了对象的数据(即状态)和行为的抽象。(2)封装性(信息隐藏)。封装性是保证软件部件具有优良的模块性的基础。 面向对象的类是封装良好的模块,类定义将其说明(用户可见的外部接口)与实现(用户不可见的内部实现)显式地分开,其内部实现按其具体定义的作用域提供保护。对象是封装的最基本单位。封装防止了程序相互依赖性而带来的变动影响。面向对象的封装比传统语言的封装更为清晰、更为有力。(3)共享性面向对象技术在不同级别上促进了共享同一类中的共享。同一类中的对象有着相同数据结构。这些对象之间是结构、行为特征的共享关系。在同一应用中共享。在同一应用的类层次结构中,存在继承关系的各相似子类中,存在数据结构和行为的继承,使各相似子类共享共同的结构和行为。使用继承来实现代码的共享,这也是面向对象的主要优点之一。在不同应用中共享。面向对象不仅允许在同一应用中共享信息,而且为未来目标的可重用设计准备了条件。通过类库这种机制和结构来实现不同应用中的信息共享。4.强调对象结构而不是程序结构'