ImageVerifierCode 换一换
格式:PPT , 页数:44 ,大小:1.80MB ,
文档编号:2227805      下载积分:25 文币
快捷下载
登录下载
邮箱/手机:
温馨提示:
系统将以此处填写的邮箱或者手机号生成账号和密码,方便再次下载。 如填写123,账号和密码都是123。
支付方式: 支付宝    微信支付   
验证码:   换一换

优惠套餐
 

温馨提示:若手机下载失败,请复制以下地址【https://www.163wenku.com/d-2227805.html】到电脑浏览器->登陆(账号密码均为手机号或邮箱;不要扫码登陆)->重新下载(不再收费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录  
下载须知

1: 试题类文档的标题没说有答案,则无答案;主观题也可能无答案。PPT的音视频可能无法播放。 请谨慎下单,一旦售出,概不退换。
2: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
3: 本文为用户(三亚风情)主动上传,所有收益归该用户。163文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知163文库(点击联系客服),我们立即给予删除!。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

1,本文(面向对象编程原则课件.ppt)为本站会员(三亚风情)主动上传,163文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。
2,用户下载本文档,所消耗的文币(积分)将全额增加到上传者的账号。
3, 若此文所含内容侵犯了您的版权或隐私,请立即通知163文库(发送邮件至3464097650@qq.com或直接QQ联系客服),我们立即给予删除!

面向对象编程原则课件.ppt

1、设计模式(1)导言:面向对象设计原则目录 1 面向对象的设计原则 2 设计模式概论 3 单件 4 观察者面向对象的设计原则 1单一职责SRP 2.OCP开闭原则 3.里氏代换LSP 4.依赖倒转DIP 5.接口隔离ISP 6.迪米特法则LOD 7合成聚合复用原则(CARP)Booch和Rumbaugh的新的“统一”标识符 单一职责SRP一个优良的系统设计,强调模块间保持低耦合、高内聚的关系,在面向对象一个优良的系统设计,强调模块间保持低耦合、高内聚的关系,在面向对象设计中这条规则同样适用,所以面向对象的第一个设计原则就是:单一职责设计中这条规则同样适用,所以面向对象的第一个设计原则就是:单一职

2、责原则(原则(SRPSRP,Single Responsibility PrincipleSingle Responsibility Principle)。)。 单一职责,强调的是职责的分离,在某种程度上对职责的理解,构单一职责,强调的是职责的分离,在某种程度上对职责的理解,构成了不同类之间耦合关系的设计关键,因此单一职责原则或多或少成为设计成了不同类之间耦合关系的设计关键,因此单一职责原则或多或少成为设计过程中一个必须考虑的基础性原则。过程中一个必须考虑的基础性原则。1.1.单一职责原则(单一职责原则(SRPSRP) 一个类,最好只做一件事,只有一个引起它变化的原因。一个类,最好只做一件事,

3、只有一个引起它变化的原因。 例如,在一个例如,在一个GameGame类中,可能会具有两个不同的职责,一个职责是维类中,可能会具有两个不同的职责,一个职责是维护创建当前轮的比赛,另一个职责是计算总比赛得分。根据护创建当前轮的比赛,另一个职责是计算总比赛得分。根据srpsrp原则,着两个原则,着两个职责应该分离到两个类中,职责应该分离到两个类中,GameGame类保持维护创建当前轮的比赛,类保持维护创建当前轮的比赛,ScorerScorer类负类负责计算比赛的得分。责计算比赛的得分。 如何要把这两个职责分离到单独的类中呢?如何要把这两个职责分离到单独的类中呢? 如果一个类承担的职责过多,等于把这些

4、职责耦合在了一起。一个职责的如果一个类承担的职责过多,等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。的设计,当变化发生时,设计会遭受到意想不到的破坏。 例如,考虑下图的设计。例如,考虑下图的设计。RetangleRetangle类具有两方法,如图。一个方法类具有两方法,如图。一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。把矩形绘制在屏幕上,另一个方法计算矩形的面积。有两个不同的Application使用Rectan

5、gle类,如上图。一个是计算几何面积的,Rectangle类会在几何形状计算方面给予它帮助。另一个Application实质上是绘制一个在舞台上显示的矩形。Rectangle类具有了两个职责,第一个职责是提供一个矩形形状几何数据模型;第二个职责是把矩形显示在屏幕上。对于SRP的违反导致了一些严重的问题。首先,我们必须在计算几何应用程序中包含核心显示对象的模块。其次,如果绘制矩形Application发生改变,也可能导致计算矩形面积Application发生改变,导致不必要的重新编译,和不可预测的失败。一个较好的设计是把这两个职责分离到下图所示的两个完全不同的类中。这个设计把Rectangle类

6、中进行计算的部分一道GeometryRectangle类中。现在矩形绘制方式的改变不会对计算矩形面积的应用产生影响了。1.1 什么是职责在在SRP中,我们把职责定义为中,我们把职责定义为“变化的原因变化的原因”(a reason for change)。如果你能够想到多于一个。如果你能够想到多于一个的动机去改变类,那么这个类就具有多于一个的职责。有时,我们很难注意到这一点。我们习惯于的动机去改变类,那么这个类就具有多于一个的职责。有时,我们很难注意到这一点。我们习惯于以组的形式去考虑职责。以组的形式去考虑职责。 class Modem public : void dial(pno:String

7、): ; void hangup(): ; void send(c:Char): ; void recv(): ; 上述上述Modem接口,大多数人会认为这个接口看起来非常合理。该接口声明了接口,大多数人会认为这个接口看起来非常合理。该接口声明了4个函数确实是个函数确实是Modem所具有的功能。然而,该接口却显示出了两个职责,一个是连接管理(所具有的功能。然而,该接口却显示出了两个职责,一个是连接管理(dial+hangup),),第二个是数据通信(第二个是数据通信(send+recv)。)。这两个职责应该被分离开么?这依赖于应用程序的变化。这两个职责应该被分离开么?这依赖于应用程序的变化。

8、是按照实际情况决定的。如果应用程序是按照实际情况决定的。如果应用程序的变化会影响连接管理,那么设计就具有僵化的臭味。因为,调用的变化会影响连接管理,那么设计就具有僵化的臭味。因为,调用send和和recv的类必须要重新编的类必须要重新编辑。在这种情况下,这两个职责应该被分离,这样做会避免这两个职责耦合在一起。辑。在这种情况下,这两个职责应该被分离,这样做会避免这两个职责耦合在一起。 另一方面,如果应用程序的变化总是导致这两方面职责同时变化,那么就不必分离他们。实际上,另一方面,如果应用程序的变化总是导致这两方面职责同时变化,那么就不必分离他们。实际上,分离他们就会具有不必要的复杂性臭味分离他们

9、就会具有不必要的复杂性臭味1.2 持久化上图展示了一种常见的违反SRP的情况,Employee类包含了业务逻辑和对于持久层的控制。这两个职责在大多数情况下决不应该混合在一起。 业务规则往往会频繁的变化,而持久化的方式却不会如此频繁的变化,并且变化的原因也是完全不同的。把业务规则和持久模块绑定在一起的做法是不妥的。当僵化性和脆弱性的臭味变得强烈,那么就应该使用FACADE和PROXY模式对设计进行重构,分离这两个职责。小结:SRP是所有原则中最简单的之一,也是最难正确应用的。我们会自然的把职责结合在一起。软件设计要做的许多内容,就是发现职责并把那些职责相互分离。分离的原则也不是教条性的,需要应实

10、际需求而定。 2.OCP开闭原则 “Closed for Modification; Open for Extension”是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的,例如以Liskov替换原则实现最佳的、正确的继承层次,就能保证不会违反开放封闭原则。 OCP的动机很简单:软件是变化的。不论是优质的设计还是低劣的设计都无法回避这一问题。OCP说明了软件设计应该尽可能地使架构稳定而又容易满足不同的需求。为什么要OCP?通常,对于开发完的代码都需要多种测试才能够投入使用,这包括:

11、 1 设计人员进行初期的架构设计 2 要经过开发人员的单元测试、集成测试。 3 然后再到测试人员的白盒测试、黑盒测试。 4 最后还要由用户进行一定的测试。经过漫长的测试,代码才能够投入使用。但是软件产品的维护和升级又是一个永恒的话题,在维护的过程中,你可能要不断地增加一些小功能;在升级的过程中,你要增加一些较大的功能。这种功能的扩展,就要求我们改变原有的代码。但是,对原代码的修改就会深刻地影响到原来的功能的方方面面: 1 可能对旧代码引入了新的错误,使你不得不对旧代码进行大规模的修改。 2 可能引起你不得不重新构造系统的架构。 3 即使新增的代码对旧代码没有影响,你也不得不对原来的系统做一个全

12、面的测试。 4 经过一段时间,也许你认为以前代码更好,更符合用户需求 所有上述列出来的问题,都是对系统功能进行扩展所不能承受的代价。换句话说,我们设计出来的系统,一定要是扩展性良好的系统。如何才能够设计出扩展性良好的系统呢?这就需要在软件系统设计时遵守开闭原则玉帝的智慧 玉帝招安美猴王 的例子 不劳师动众、不破坏天规便是“闭”,收仙有道便是“开”。招安之法便是玉帝天庭的“开一闭”原则,通过给美猴王封一个“弼马温”的官职,便可使现有系统满足变化了的需求,而不必更改天庭的既有秩序 如何在OO中引入OCP原则?把对实体的依赖改为对抽象的依赖就行了。下面的例子说明了这个过程:05赛季的时候,一辆F1赛

13、车有一台V10引擎。但是到了06赛季,国际汽联修改了规则,一辆F1赛车只能安装一台V8引擎。车队很快投入了新赛车的研发,不幸的是,从工程师那里得到消息,旧车身的设计不能够装进新研发的引擎。我们不得不为新的引擎重新打造车身,于是一辆新的赛车诞生了。但是,麻烦的事接踵而来,国际汽联频频修改规则,搞得设计师在“赛车”上改了又改,最终变得不成样子,只能把它废弃。 为了能够重用这辆昂贵的赛车,工程师们提出了解决方案:首先,在车身的设计上预留出安装引擎的位置和管线。然后,根据这些设计好的规范设计引擎(或是引擎的适配器)。于是,新的赛车设计方案就这样诞生了。 做到开闭原则,就注意以下两点。1)多使用抽象类在

14、设计类时,对于拥有共同功能的相似类进行抽象化处理,将公用的功能部分放到抽象类中,所有的操作都调用子类。这样,在需要对系统进行功能扩展时,只需要依据抽象类实现新的子类即可。如图10-1所示,在扩展子类时,不仅可以拥有抽象类的共有属性和共有函数,还可以拥有自定义的属性和函数。2)多使用接口 与抽象类不同,接口只定义子类应该实现的接口函数,而不实现公有的功能。在现在大多数的软件开发中,都会为实现类定义接口,这样在扩展子类时实现该接口。如果要改换原有的实现,只需要改换一个实现类即可。 如图各子类由接口类定义了接口函数,只需要在不同的子类中编写不同的实现即可,当然也可以实现自有的函数。Liskov(女程

15、序员)替换原则 在一个软件系统中,子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。 第一个例子:正方形不是长方形第一个例子:正方形不是长方形 “正方形不是长方形”是一个理解里氏代换原则的最经典的例子。在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统中,让正方形继承自长方形是顺利成章的事情。现在,我们截取该系统的一个代码片段进行分析: 正方形不是长方形正方形不是长方形长方形类Rectangle:class Rectangle double length;double width;public : double

16、 getLength() return length; void setLength(double height) this.length = length; double getWidth() return width; void setWidth(double width) this.width = width; 正方形类Square:class Square :public Rectangle public : void setWidth(double width) Rectangle :setLength(width); Rectangle : setWidth(width); voi

17、d setLength(double length) Rectangle :.setLength(length); Rectangle :.setWidth(length); 正方形不是长方形正方形不是长方形由于正方形的度和宽度必须相等,所以在方法setLength和setWidth中,对长度和宽度赋值相同。类TestRectangle是我们的软件系统中的一个组件,它有一个resize方法要用到基类Rectangle,resize方法的功能是模拟长方形宽度逐步增长的效果:测试类TestRectangle:class TestRectangle public: void resize(Recta

18、ngle &objRect) while(objRect.getWidth() = objRect.getLength() ) objRect.setWidth( objRect.getWidth () + 1 ); 正方形不是长方形正方形不是长方形 我们运行一下这段代码就会发现,假如我们把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的

19、,正方形不适合。 我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。 鸵鸟不是鸟 “鸵鸟非鸟”也是一个理解里氏代换原则的经典的例子。“鸵鸟非鸟”的另一个版本是“企鹅非鸟”,这两种说法本质上没有区别,前提条件都是这种鸟不会飞。生物学中对于鸟类的定义:“恒温动物,卵生,全身披有羽毛,身体呈流线形,有角质的喙,眼在头的两侧。前肢退化成翼,后肢有鳞状外皮,有四趾”。所以,从生物学角度来看,鸵鸟肯定是一

20、种鸟。我们设计一个与鸟有关的系统,鸵鸟类顺理成章地由鸟类派生,鸟类所有的特性和行为都被鸵鸟类继承。大多数的鸟类在人们的印象中都是会飞的,所以,我们给鸟类设计了一个名字为fly的方法,还给出了与飞行相关的一些属性,比如飞行速度(velocity)。鸟类Bird:class Bird double velocity; public : void fly() /I am flying; ; void setVelocity(double velocity) this.velocity = velocity; ; double getVelocity() return this.velocity; ;

21、 鸵鸟不会飞怎么办?我们就让它扇扇翅膀表示一下吧,在fly方法里什么都不做。至于它的飞行速度,不会飞就只能设定为0了,于是我们就有了鸵鸟类的设计。 鸵鸟类Ostrich:class Ostrich :public Bird public fly() /I do nothing; ; public setVelocity(double velocity) this.velocity = 0; ; public getVelocity() return 0; ;鸵鸟不是鸟好了,所有的类都设计完成,我们把类Bird提供给了其它的代码(消费者)使用。现在,消费者使用Bird类完成这样一个需求:计算鸟飞

22、越黄河所需的时间。 对于Bird类的消费者而言,它只看到了Bird类中有fly和getVelocity两个方法,至于里面的实现细节,它不关心,而且也无需关心,于是给出了实现代码: 测试类TestBird:class TestBird public: void calcFlyTime(Bird bird) try double riverWidth = 3000; cout.riverWidth / bird.getVelocity()endl; catch() coutAn error occured!endl ; ;鸵鸟不是鸟 如果我们拿一种飞鸟来测试这段代码,没有问题,结果正确,符合我们的

23、预期,系统输出了飞鸟飞越黄河的所需要的时间;如果我们再拿鸵鸟来测试这段代码,结果代码发生了系统除零的异常,明显不符合我们的预期。 对于TestBird类而言,它只是Bird类的一个消费者,它在使用Bird类的时候,只需要根据Bird类提供的方法进行相应的使用,根本不会关心鸵鸟会不会飞这样的问题,而且也无须知道。它就是要按照“所需时间 = 黄河的宽度 / 鸟的飞行速度”的规则来计算鸟飞越黄河所需要的时间。 我们得出结论:在calcFlyTime方法中,Bird类型的参数是不能被Ostrich类型的参数所代替,如果进行了替换就得不到预期结果。因此,Ostrich类和Bird类之间的继承关系违反了里

24、氏代换原则,它们之间的继承关系不成立,鸵鸟不是鸟。 4.4 鸵鸟到底是不是鸟鸵鸟到底是不是鸟? “鸵鸟到底是不是鸟”,鸵鸟是鸟也不是鸟,这个结论似乎就是个悖论。产生这种混乱有两方面的原因:原因一:对类的继承关系的定义没有搞清楚。原因一:对类的继承关系的定义没有搞清楚。 面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。 类的继承关系就是一种类的继承关系就是一种“Is-A”关系,实际上指的是行为上的关系,实际上指的是行为上的“Is-A”关关系,可以把它描述为系,可以把它描述为“Act-As”。 我们再来看“正方形不是长方形”这个例子,正方

25、形在设置长度和宽度这两个行为上,与长方形显然是不同的。长方形的行为:设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到基类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。 “鸵鸟非鸟”基本上也是同样的道理。我们一讲到鸟,就认为它能飞,有的鸟确实能飞,但不是所有的鸟都能飞。问题就是出在这里。如果以“飞”的行为作为衡量“鸟”的标准的话,鸵鸟显然不是鸟;如果按照生物学的划分标准:有翅膀、有羽毛等特性作为衡量“

26、鸟”的标准的话,鸵鸟理所当然就是鸟了。鸵鸟没有“飞”的行为,我们强行给它加上了这个行为,所以在面对“飞越黄河”的需求时,代码就会出现运行期故障。 鸵鸟到底是不是鸟鸵鸟到底是不是鸟?原因二:设计要依赖于用户要求和具体环境。原因二:设计要依赖于用户要求和具体环境。 继承关系要求子类要具有基类全部的行为。这里的行为是指落在需求范围内的行为。图中鸟类具有4个对外的行为,其中2个行为分别落在A和B系统需求中: 系统需求和对象关系示意图系统需求和对象关系示意图 A需求期望鸟类提供与飞翔有关的行为,即使鸵鸟跟普通的鸟在外观上就是100%的相像,但在A需求范围内,鸵鸟在飞翔这一点上跟其它普通的鸟是不一致的,它

27、没有这个能力,所以,鸵鸟类无法从鸟类派生,鸵鸟不是鸟。 B需求期望鸟类提供与羽毛有关的行为,那么鸵鸟在这一点上跟其它普通的鸟一致的。虽然它不会飞,但是这一点不在B需求范围内,所以,它具备了鸟类全部的行为特征,鸵鸟类就能够从鸟类派生,鸵鸟就是鸟。 所有派生类的行为功能必须和使用者对其基类的期望保持一致,如果派生类达不到这一点,那么必然违反里氏替换原则。在实际的开发过程中,不正确的派生关系是非常有害的。伴随着软件开发规模的扩大,参与的开发人员也越来越多,每个人都在使用别人提供的组件,也会为别人提供组件。最终,所有人的开发的组件经过层层包装和不断组合,被集成为一个完整的系统。每个开发人员在使用别人的

28、组件时,只需知道组件的对外裸露的接口,那就是它全部行为的集合,至于内部到底是怎么实现的,无法知道,也无须知道。所以,对于使用者而言,它只能通过接口实现自己的预期,如果组件接口提供的行为与使用者的预期不符,错误便产生了。里氏代换原则就是在设计时避免出现派生类与基类不一致的行为。如何正确地运用里氏代换原则如何正确地运用里氏代换原则 里氏代换原则目的就是要保证继承关系的正确性。我们在实际的项目中,是不是对于每一个继承关系都得费这么大劲去斟酌?不需要,大多数情况下按照“Is-A”去设计继承关系是没有问题的,只有极少的情况下,需要你仔细处理一下,这类情况对于有点开发经验的人,一般都会觉察到,是有规律可循

29、的。最典型的就是使用者的代码中必须包含依据子类类型执行相应的动作的代码:动物类Animal: class Animalstring name;public: void Animal(String name) this.name = name; void printName() try coutI am a + name + !“endl; catch() coutAn error occured!“endl; 猫类Cat:class Cat :public Animalpublic: Cat(String name) :Animal(name) void Mew() trycout Mew “

30、endl; catch()coutAn error occured!“endl; 狗类Dog:public class Dog :public Animal Dog(String name) : Animal (name) void Bark() try coutBark “endl; catch() coutAn error occured!“抽象层抽象层-底层类底层类。 (High Level Classes(高层模块) - Abstraction Layer(抽象接口层) - Low Level Classes(低层模块)。缺点:耦合太紧密,Light发生变化将影响ToggleSwitc

31、h。 解决办法一将Light作成Abstract,然后具体类继承自Light。优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根据“开放封闭”原则进行扩展。 只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。解决办法一接口隔离ISP一、一、ISP简介(简介(ISP-Interface Segregation Principle):):第一:客户端不应该依赖他不需要

32、的接口也就是对接口的细化 纯洁;第二:类直接的依赖应该建立在最小的接口上面;第三:建立单一的接口 几个模块就要有及格接口 而不是一个庞大的臃肿的接口;其他:接口是对外的承诺,承诺的越少,月利于开发;但是开发的过程中也要注意一个度的概念,否则接口太多也不利于维护;在我们进行设计的时候,一个重要的工作就是恰当地划分角色和角色对应的接口。因此,这里的接口往往有两种不同的含义。 二、举例说明:二、举例说明:1接口对应的角色指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象,接口的划分就直接带来类型的划分。这里,我们可以把接口理解成角色,一个接口只是代表一个角色,每个角色都有它特定的一个接口,这里

33、的这个原则可以叫做角色隔离原则。例如,我们将电脑的所有功能角色集合为一起,构建了一个接口,如图10-3所示。 此时,我的电脑和你的电脑要实现该接口,就必须实现所有的接口函数,显然接口混乱,并不能够满足实际的需求: 我的电脑可能是用来工作和学习的,你的电脑可能是用来看电影、上网和打游戏等娱乐活动的,那我们就可以将电脑的角色划分为两类,如图10-4所示。 2角色对应的接口指某种语言具体的接口定义,有严格的定义和结构。比如Java语言里面的Interface结构。对不同的客户端,同一个角色提供宽窄不同的接口,也就是定制服务,仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来。对于图10-4中的接

34、口定义,如果我的电脑除了工作和学习之外,还想上网,那就没办法了,必须实现娱乐电脑的接口,这样就必须实现它的所有接口函数了。此时我们需要将对应角色中的接口再进行划分,如图10-5所示。 这样,经过以上的划分,如果我的电脑想增加某一项功能,只需要继承不同的接口类即可。 由此可见,对接口角色的划分,是从大的类上进行划分的;对角色的接口进行的划分,是对类的接口函数的划分。它们两者由粗到细,实现了接口的完全分离。迪米特法则迪米特法则(Law of Demeter LoD)又叫做最少知识原则又叫做最少知识原则(Least Knowledge Principle,LKP),就是说,一个对象应当对其他对象有尽

35、可能少的了了解.迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则LIEB89LIEB86.这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:(1)只与你直接的朋友们通信(Only talk to your immediate friends)(2)不要跟陌生人说话(Dont talk to strangers)(3)每一个软件单位对其

36、他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。 合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP) 定义:在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用这些对象的目的。应首先使用合成/聚合,合成/聚合则使系统灵活,其次才考虑继承,达到复用的目的。而使用继承时,要严格遵循里氏代换原则。有效地使用继承会有助于对问题的理解,降

37、低复杂度,而滥用继承会增加系统构建、维护时的难度及系统的复杂度。如果两个类是“Has-a”关系应使用合成、聚合,如果是“Is-a”关系可使用继承。“Is-A”是严格的分类学意义上定义,意思是一个类是另一个类的“一种”。而“Has-A”则不同,它表示某一个角色具有某一项责任。 什么是合成?什么是聚合?合成(Composition)和聚合(Aggregation)都是关联(Association)的特殊种类。聚合表示整体和部分的关系,表示“拥有”。如奔驰S360汽车,对奔驰S360引擎、奔驰S360轮胎的关系是聚合关系,离开了奔驰S360汽车,引擎、轮胎就失去了存在的意义。在设计中, 聚合不应该频

38、繁出现,这样会增大设计的耦合度。合成则是一种更强的“拥有”,部分和整体的生命周期一样。合成的新的对象完全支配其组成部分,包括它们的创建和湮灭等。一个合成关系的成分对象是不能与另一个合成关系共享的。换句话说,合成是值的聚合(Aggregation by Value),而一般说的聚合是引用的聚合(Aggregation by Reference)。明白了合成和聚合关系,再来理解合成/聚合原则应该就清楚了,要避免在系统设计中出现,一个类的继承层次超过3层,则需考虑重构代码,或者重新设计结构。当然最好的办法就是考虑使用合成/聚合原则。 通过合成/聚合的优缺点 优点:1) 新对象存取成分对象的唯一方法是

39、通过成分对象的接口。2) 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。3) 这种复用支持包装。4) 这种复用所需的依赖较少。5) 每一个新的类可以将焦点集中在一个任务上。6) 这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。7) 作为复用手段可以应用到几乎任何环境中去。 缺点:就是系统中会有较多的对象需要管理。 通过继承来进行复用的优缺点 优点:1)新的实现较为容易,因为超类的大部分功能可以通过继承的关系自动进入子类。2)修改和扩展继承而来的实现较为容易。 缺点:1)继承复用破坏包装,因为继承将超类的实现细节暴露给子类。由于超类的内部细节常常是

40、对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。2)如果超类发生改变,那么子类的实现也不得不发生改变。3)从超类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。4)继承只能在有限的环境中使用。 我们看一个例子。如果我们把“人”当成一个类,然后把“雇员”,“经理”,“学生”当成是“人”的子类。这个的错误在于把“角色”的等级结构和“人”的等级结构混淆了。“经理”,“雇员”,“学生”是一个人的角色,一个人可以同时拥有上述角色。如果按继承来设计,那么如果一个人是雇员的话,就不可能是经理,也不可能是学生,这显然不合理。正确的设计是有个抽象类“角色”,“人”可以拥有多个“角色”(聚合),“雇员”,“经理”,“学生”是“角色”的子类。另外一个就是只有两个类满足里氏代换原则的时候,才可能是“IsA”关系。也就是说,如果两个类是“Has-A”关系,但是设计成了继承,那么肯定违反里氏代换原则。

侵权处理QQ:3464097650--上传资料QQ:3464097650

【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。


163文库-Www.163Wenku.Com |网站地图|