面向对象程序实现-继承与多态性(I).ppt

上传人:re****.1 文档编号:568319184 上传时间:2024-07-24 格式:PPT 页数:127 大小:2.42MB
返回 下载 相关 举报
面向对象程序实现-继承与多态性(I).ppt_第1页
第1页 / 共127页
面向对象程序实现-继承与多态性(I).ppt_第2页
第2页 / 共127页
面向对象程序实现-继承与多态性(I).ppt_第3页
第3页 / 共127页
面向对象程序实现-继承与多态性(I).ppt_第4页
第4页 / 共127页
面向对象程序实现-继承与多态性(I).ppt_第5页
第5页 / 共127页
点击查看更多>>
资源描述

《面向对象程序实现-继承与多态性(I).ppt》由会员分享,可在线阅读,更多相关《面向对象程序实现-继承与多态性(I).ppt(127页珍藏版)》请在金锄头文库上搜索。

1、9-1Lecture Notes onObject-Oriented Technology(Programming & Design)(Fall 2010, Bachelor of Computer Science)Duan Office: Room 1203A ,Information Building9-2第八讲 继承与多态性(I)软件复用及其途径继承:泛化的实现 继承中的对象初始化与收尾 多继承与重复继承继承的实例:纸牌游戏主题讨论:存储模型,与继承相关的两个设计原则9-3两种is-a关系分类(classification):描述实例与类型之间的关系。Tommyisacat.泛化(gen

2、eralization):描述类型与类型之间的关系(子类型关系)。Catsareanimals.注意它们的区别泛化关系具有传递性;而分类关系不具有传递性。继承所指的is-a关系是指泛化,而不是分类。有学者提出新名词:用kind-of关系代替is-a关系。Thecatiaakindofanimals.9-4继承是is-a关系在程序中的实现is-a关系:在问题空间中描述概念与概念之间的关系概念与概念之间的关系。 利用现有概念来定义一个新的概念。继承关系:在解空间中描述类与类之间的关系类与类之间的关系。 利用现有的类来定义一个新的类。软件复用的思想面向对象设计的一个重要指导原则是:不要每次都从头开始

3、定义一个新的类,而是将这个新的类作为一个或若干个现有类的泛化或特化。继承机制9-5class X int i; public: X( ) i = 0; void f();class Y int j;public: X x; Y( ) i = 0; ; i X的对象iY的对象j子对象xmain() Y y; y.x.f();(composition)9-6继承继承class Box public: int width, height; void SetWidth(int); void SetHeight(int);class ColoredBox:public Boxpublic: int co

4、lor; void SetColor(int);ColoredBox cb;void main() cb.SetWidth(5); cb.SetHeight(5); cb.SetColor(6);)9-7classCleanserprivate: strings;protected: voidappend(stringa)s+=a;public: Cleanser() s=“Cleanser”; voiddilute()append(dilute();voidapply()append(apply(); voidscrub()append(scrub(); voidprint()coutssc

5、rub();/!Detergent*d=newCleanser();/!D-foam();incompatible typesincompatible types9-11静态类型 vs 动态类型静态类型:声明obj时的类型(在编译时确定的类型)。动态类型:在运行时某一时刻与obj相关联的对象的类型。 由于基本类型的静态与动态类型没有区别,故只需讨论引用类型。动态类型必须是静态类型的子类型!Cleanser*c=newDetergent();cc的静态类型的静态类型cc的动态类型的动态类型9-12对继承的狙击对继承的狙击有时候并不希望由客户程序自定义的子类代替某些类! Java里有很好的机制将一

6、个类定义为final的或者将一个成员定义 为final的。 C+里如何实现禁止类被派生?解决方案解决方案1 构造函数声明为私有的。如果用户从该类派生一个类,那么在编译阶段就会得到一个不能访问私有成员函数的错误信息。 2 创建伪造的构造函数:静态,返回的是对象指针3 注意:用户在使用完该类对象后需要调用delete,释放资源。也可使用智能指针。9-13classClxNotBasepublic:ClxNotBase();private:ClxNotBase();ClxNotBase(constClxNotBase&rhs);构造函数构造函数私有,禁私有,禁止派生止派生如果把类的构造函数声明为私有

7、的,那么如果把类的构造函数声明为私有的,那么我们就无法构造这个类的对象,我们就无法构造这个类的对象, 9-14classClxNotBasepublic:ClxNotBase();staticClxNotBase*NewlxNotBase();staticClxNotBase*NewlxNotBase(constClxNotBase&rhs);private:ClxNotBase();ClxNotBase(constClxNotBase&rhs);ClxNotBase*ClxNotBase:NewlxNotBase()/调用真正的构造函数returnnewClxNotBase();ClxNot

8、Base*ClxNotBase:NewlxNotBase(constClxNotBase&rhs)/调用真正的拷贝构造函数returnnewClxNotBase(rhs);创建伪造创建伪造的构造函的构造函数数如果把类的构造函数声明为私有的,那么如果把类的构造函数声明为私有的,那么我们就无法构造这个类的对象,我们就无法构造这个类的对象, 9-15两种不同的继承模式 重定义(overriding):仅重定义父类的操作而不引入新特征。 是比扩充更为重要、更加常见的继承模式。 子类型的接口与父类型完全相同,两者是完全相同的类型。 扩充(extending):引入父类所没有的新特征。 子类型与父类型有区

9、别:更加丰富的内容,是父类型的特例。 Java语言的保留字extends表明了这种继承。 实际应用通常是上述两种方式的结合。继承的模式9-16重定义的继承模式(is-a)Rectangledraw()FigureTriangleCircledraw()draw()draw()9-17扩充的继承模式(is-like-a)Pointx, y: int;LocationCirclevisible: boolean;radius: double;注意圆与点之间注意圆与点之间关系不是关系不是has-ahas-a关系关系将圆看作一种将圆看作一种带有半径的点带有半径的点9-19例例: 定义基类定义基类Pen

10、class Penpublic: enum ink off,on; void set_status (ink); void set_location (int, int);private: int x; int y; int status; /状态;xystatusset_statusset_location9-20定义派生类定义派生类(彩色钢笔彩色钢笔)class CPen: public Pen public:void set_color(int);private:int color;xystatusset_statusset_locationcolorset_color9-21继承概念继

11、承概念1 - 1 - 继承方式继承方式 三种继承方式三种继承方式:公有继承公有继承(public),私有继承私有继承(private),保护继承保护继承(protected)不同继承方式的影响主要体现在:不同继承方式的影响主要体现在: 派生类成员对从基类继承的成员的访问控制。派生类成员对从基类继承的成员的访问控制。 派生类对象对从基类继承成员的访问控制。派生类对象对从基类继承成员的访问控制。9-22继承方式继承方式继承的访问控制继承的访问控制 z不不同同的的继继承承方方式式使使得得派派生生类类从从基基类类继继承承的的成成员员具具有有不不同同的的访访问问控控制制权权限限,以以实实现现数据的安全性

12、和共享性控制。数据的安全性和共享性控制。z派派生生类类成成员员(继继承承的的成成员员自自增增加加的的成成员)的访问权限员)的访问权限:y1)inaccessible(不可访问)y2)publicy3)privatey4)protected9-239-48子类继承了什么子类继承了父类和所有成员,包括方法和变量(域)。 构造方法并不是一个类的成员,所以没有被继承。在C+中不能被继承的部分1、构造函数; 2、析构函数; 3、用户定义的操作符; 4、用户定义的赋值符; 5、友元关系。构造方法未被继承9-52派生类的构造函数派生类的构造函数z派生类构造函数的一般形式: 派派生生类类名名:派派生生类类名名

13、(基基类类所所需需的的形形参参,本本类类成成员员所所需需的的形形参参):基类基类1(基类参数表基类参数表1), ,基类基类n(基类参数表基类参数表n),对象成员对象成员1(对象参数表对象参数表1), ,对象成员对象成员m(对象参数表对象参数表m) 本类基本类型数据成员初始化;本类基本类型数据成员初始化; 1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。2调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。3 派生类的构造函数体中的内容。9-60多继承的经典例子(C+语言的iostream.h)istreamostreamiostream9-61派生类的定义格式派生

14、类的定义格式 class class 派生类名:继承方式派生类名:继承方式 基类名基类名1, 1, 继承方式继承方式 基类名基类名n n public: public: / /派生类公有成员派生类公有成员 private: private: / /派生类私有成员派生类私有成员 ;有多个基类有多个基类派生类有多派生类有多个基类为个基类为多继承多继承9-62注意注意z每一个每一个“继承方式继承方式”,只用于限制对紧随,只用于限制对紧随其后之基类的继承。其后之基类的继承。z类类的的继继承承方方式式是是派派生生类类对对基基类类成成员员的的继继承承方式。方式。z类类的的继继承承方方式式指指定定了了类类外

15、外对对象象对对于于派派生生类类从基类继承来的成员的访问权限。从基类继承来的成员的访问权限。z缺省的继承方式:私有缺省的继承方式:私有9-63二义性问题举例class A public: void f( );class B public: void f( ); void g( );class C: public A, public B public: void g( ); void h( );如果声明:C c1;则 c1.f( ); 具有二义性而 c1.g( ); 无二义性(同名覆盖)9-64二义性的解决方法z解决方法一:用类名来限定c1.A:f( ) 或 c1.B:f( )z解决方法二:同名覆

16、盖在C 中声明一个同名成员函数f( ),f( )再根据需要调用 A:f( ) 或 B:f( )9-65重复继承的经典例子(C+语言的iostream.h)istreamostreamiosiostream9-66二义性问题举例class B public: int b;class B1 : public B private: int b1;class B2 : public B private: int b2;class C : public B1,public B2 public: int f( ); private: int d;9-67下面的访问是二义性的:C c;c.bc.B:b下面是

17、正确的:c.B1:bc.B2:b9-68派生类C的对象的存储结构示意图:bb1bb2dB类成员B类成员B1类成员B2类成员C类对象9-69虚基类z虚基类的引入虚基类的引入y用于有共同基类的场合z声明声明y以virtual修饰说明基类例:class B1:virtual public Bz作用作用y主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.y为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝z注意:注意:y在第一级继承时就要将共同基类设计为虚基类。9-70虚基类举例class B private: int b;class B1 : virtual public B

18、private: int b1;class B2 : virtual public B private: int b2;class C : public B1, public B2 private: float d;下面的访问是正确的:C cobj;cobj.b;9-71虚基类的派生类对象存储结构示意图:BB1B2Cb1b2dB1类成员B2类成员C类对象bB类成员9-75有虚基类时的构造函数举例#include class B0/声明基类B0 public:/外部接口B0(int n) nV=n;int nV;void fun( )coutMember of B0endl;9-76class

19、B1: virtual public B0 public:B1(int a) : B0(a) int nV1;class B2: virtual public B0 public:B2(int a) : B0(a) int nV2;9-77class D1: public B1, public B2public:D1(int a) : B0(a), B1(a), B2(a)int nVd;void fund( )coutMember of D1只有与这个类本身及其子类相关的成员函数才可以对此数只有与这个类本身及其子类相关的成员函数才可以对此数据字段进行存取。据字段进行存取。ztop()、 po

20、p()和和 isEmpty()这三个函数通过这三个函数通过 Stack 类所提供的成员函数来操纵纸牌列表。类所提供的成员函数来操纵纸牌列表。注意:这注意:这3种方种方法被声明为法被声明为 final. y文本提示:子类不能重写。文本提示:子类不能重写。yJava compiler 能够优化能够优化 final methods, 代码执行效率更代码执行效率更高。高。9-90Informal Specification of CardPiles Non-final Methodszselect(x,y) yperforms an action in response to mouse click

21、at (x,y)zincludes(x,y) ydetermines whether (x,y) within boundary of the pile zcanTake(c) ydetermines whether the pile can take card c (according to the rules governing the pile) zaddCard(c) yadds card c to the card pile zdisplay(g) ydisplays the pile using graphics context g 9-91Card Piles zIncludes

22、- y给定坐标位置作为参数,决定该位置是否包含于牌给定坐标位置作为参数,决定该位置是否包含于牌堆的边界之内。缺省的行为只检测最上面纸牌。堆的边界之内。缺省的行为只检测最上面纸牌。y在处理桌面堆时,将改写这个方法,以检测所有的在处理桌面堆时,将改写这个方法,以检测所有的纸牌。纸牌。zcanTake- y返回关于牌堆是否可以取走特定纸牌的结果。只有返回关于牌堆是否可以取走特定纸牌的结果。只有桌面堆和花色堆才可以取走纸牌,因此缺省行为是桌面堆和花色堆才可以取走纸牌,因此缺省行为是返回否定的结果。返回否定的结果。y在关于桌面堆和花色堆的这两个类中,将对此方法在关于桌面堆和花色堆的这两个类中,将对此方法

23、进行改写。进行改写。9-92Card Piles zaddCard-y增加一张纸牌到纸牌列表。增加一张纸牌到纸牌列表。y在关于丢弃堆的类中,将对此方法进行重定义,以在关于丢弃堆的类中,将对此方法进行重定义,以确保纸牌正面朝上。确保纸牌正面朝上。zDisplay- y显示整副纸牌。缺省方法仅仅显示牌堆的最上面纸牌;显示整副纸牌。缺省方法仅仅显示牌堆的最上面纸牌;y但是在关于桌面堆的类中将对其进行改写,以显示一列纸牌。但是在关于桌面堆的类中将对其进行改写,以显示一列纸牌。每张被覆盖的纸牌都会显示一半牌面。因此,我们将显示界每张被覆盖的纸牌都会显示一半牌面。因此,我们将显示界面简化成只显示最上面正面

24、朝上的纸牌和最下面正面朝上的面简化成只显示最上面正面朝上的纸牌和最下面正面朝上的纸牌(这样可以给出纸牌界面的明确边界)。纸牌(这样可以给出纸牌界面的明确边界)。9-93Card Piles zselect- y执行响应鼠标单击的行为。当用户对牌堆所覆盖的游戏执行响应鼠标单击的行为。当用户对牌堆所覆盖的游戏区域单击鼠标选择纸牌时,将调用此方法。区域单击鼠标选择纸牌时,将调用此方法。y缺省行为是不做任何事情,缺省行为是不做任何事情,y在关于桌面堆、待用堆和丢弃堆的类中,将对其进行改在关于桌面堆、待用堆和丢弃堆的类中,将对其进行改写,如果有可能,将移动最上面的纸牌。写,如果有可能,将移动最上面的纸牌

25、。9-94Power of Inheritance z继承的重要作用。继承的重要作用。z对于给定的对于给定的 5 个操作和个操作和 5 个类,共存在个类,共存在 25 种可能需种可能需要定的方法。通过使用继承,我们只需要实现要定的方法。通过使用继承,我们只需要实现 13 个方个方法法z可以确保对于相似的请求,不同的牌堆将以相同的方可以确保对于相似的请求,不同的牌堆将以相同的方式进行响应。式进行响应。9-95Suit Piles z类类 suitPile 是最简单的子类,代表游戏界面中的上方牌堆,由是最简单的子类,代表游戏界面中的上方牌堆,由花色从花色从 A K 的纸牌组成。的纸牌组成。defi

26、nes only two methods yThe constructor :接收:接收2个参数,调用父类个参数,调用父类CardPile的构的构造函数造函数 。y方法方法 canTake 改写了父类的同名方法。这个方法决定一张改写了父类的同名方法。这个方法决定一张纸牌是否可以放在指定的牌堆上面。只有纸牌点数为纸牌是否可以放在指定的牌堆上面。只有纸牌点数为 A (即(即点数为点数为 0 )且牌堆为空,或者纸牌具有与牌堆相同的花色且)且牌堆为空,或者纸牌具有与牌堆相同的花色且点数比最上面纸牌多点数比最上面纸牌多1点(例如,黑桃点(例如,黑桃 3 只能放于黑桃只能放于黑桃 2 的的上面),该纸牌才

27、可以放于牌堆的上面。上面),该纸牌才可以放于牌堆的上面。9-96The DeckPilezDeckPile 包含所有待取用的纸牌。它与一般牌堆之间包含所有待取用的纸牌。它与一般牌堆之间有两点不同之处。有两点不同之处。y当当构造构造待用堆时,不是创建一个空的纸牌堆,而是需要创建待用堆时,不是创建一个空的纸牌堆,而是需要创建一个数组,这个数组由一副传统的一个数组,这个数组由一副传统的 52 张纸牌组成,以此来张纸牌组成,以此来实现牌堆的初始化。实现牌堆的初始化。y然后,需要随机地从这个集合中选择纸牌,来建立一副有序然后,需要随机地从这个集合中选择纸牌,来建立一副有序的纸牌。当使用鼠标选择纸牌时,将

28、调用的纸牌。当使用鼠标选择纸牌时,将调用 select 方法。如果方法。如果牌堆为空,则不做任何事情。否则,最上面纸牌将移走,置牌堆为空,则不做任何事情。否则,最上面纸牌将移走,置于丢弃堆的上面。于丢弃堆的上面。9-97The DeckPilezselect 方法的实现使我们又遇到了一个新问题。方法的实现使我们又遇到了一个新问题。z当在当在待用堆待用堆纸牌上按下鼠标键时,理想行为是将纸牌纸牌上按下鼠标键时,理想行为是将纸牌由待用堆移到丢弃堆,并将纸牌翻转,使其正面朝上。由待用堆移到丢弃堆,并将纸牌翻转,使其正面朝上。问题是,问题是,现在需要一对象,来表示丢弃堆这个惟一的现在需要一对象,来表示丢

29、弃堆这个惟一的牌堆。牌堆。y一个方法是定义不同的牌堆对象作为全局变量,可以对其进一个方法是定义不同的牌堆对象作为全局变量,可以对其进行普遍的存取。行普遍的存取。x但是,但是, 例如例如 Java 和和 C 语言,并不支持全局变量。语言,并不支持全局变量。x而且,由于在程序的任何位置都可以对全局变量进行存取,因此全局而且,由于在程序的任何位置都可以对全局变量进行存取,因此全局变量的使用将使程序的信息流变得难以理解。变量的使用将使程序的信息流变得难以理解。y另一个更好的、更加面向对象的、代替全局变量的解决方案另一个更好的、更加面向对象的、代替全局变量的解决方案就是就是x使用一系列的静态变量这可以将

30、全局值的数目减少到一个:类名。通使用一系列的静态变量这可以将全局值的数目减少到一个:类名。通过类的静态方法来存取更多的状态。过类的静态方法来存取更多的状态。9-98The DeckPile9-99DiscardPilezselect 方法改写(或者替换)了方法改写(或者替换)了 CardPile 类的缺省行为,当类的缺省行为,当调用此方法时(即在纸牌上单击鼠标时),调用此方法时(即在纸牌上单击鼠标时),将检查牌堆最上面将检查牌堆最上面的纸牌是否可以移动到花色堆上或者桌面堆上。的纸牌是否可以移动到花色堆上或者桌面堆上。如果不符合移如果不符合移动条件,纸牌将保留在原来的丢弃堆上。动条件,纸牌将保留

31、在原来的丢弃堆上。z方法方法 addCard 则使用另外一种不同的改写形式。在这里,父类则使用另外一种不同的改写形式。在这里,父类的缺省行为得到进一步的精炼。的缺省行为得到进一步的精炼。y不仅要完全执行父类的行为,而且还要增加新的行为。确保当纸牌放于丢不仅要完全执行父类的行为,而且还要增加新的行为。确保当纸牌放于丢弃堆时,总是正面朝上的。满足这一条件之后,将继续调用父类的代码,弃堆时,总是正面朝上的。满足这一条件之后,将继续调用父类的代码,将纸牌加入丢弃堆中。对于将纸牌加入丢弃堆中。对于 Java 语言,通过发送消息给语言,通过发送消息给super ,即,即 super .addcard(aC

32、ard)来解决这样的问题。来解决这样的问题。z另外还有一种改写形式,出现在各种子类的构造函数中。每个另外还有一种改写形式,出现在各种子类的构造函数中。每个子类的构造函数在执行自己特有的行为之前,都必须调用父类子类的构造函数在执行自己特有的行为之前,都必须调用父类的构造函数来确保对象得以正确地初始化。通过子类构造函数的构造函数来确保对象得以正确地初始化。通过子类构造函数中的初始化子句来实现对父类构造函数的调用。中的初始化子句来实现对父类构造函数的调用。9-100DiscardPile9-101tableau Piles z最复杂的子类最复杂的子类,需要需要对父类中的几乎所有的虚拟方法都进行重新定

33、义。对父类中的几乎所有的虚拟方法都进行重新定义。z当通过构造函数进行初始化时,桌面堆将从待用堆中取走一定数量的当通过构造函数进行初始化时,桌面堆将从待用堆中取走一定数量的纸牌,将其放于自己所代表的牌堆中。取走的纸牌数量决定于构造函纸牌,将其放于自己所代表的牌堆中。取走的纸牌数量决定于构造函数的附加参数。然后,将桌面堆的最上面纸牌显示为正面朝上。数的附加参数。然后,将桌面堆的最上面纸牌显示为正面朝上。z只有纸牌点数为只有纸牌点数为 K 且牌堆为空,或者纸牌具有与牌堆当前最上面纸牌且牌堆为空,或者纸牌具有与牌堆当前最上面纸牌相反的花色且点数少相反的花色且点数少 1 点,该纸牌才可以放于牌堆的上面点

34、,该纸牌才可以放于牌堆的上面(canTake)。)。z当检测到单击鼠标的行为时,需判断单击的位置是否包含于牌堆范围当检测到单击鼠标的行为时,需判断单击的位置是否包含于牌堆范围(方法(方法 indudes ) ,但由于每列牌堆是变长的,因此不需检查牌堆,但由于每列牌堆是变长的,因此不需检查牌堆的下边界。当选定牌堆后,如果最上面的纸牌是背面朝上,就将其翻的下边界。当选定牌堆后,如果最上面的纸牌是背面朝上,就将其翻转。如果是正朝上,就首先尝试将其移动到任一可用的花色堆,然后转。如果是正朝上,就首先尝试将其移动到任一可用的花色堆,然后再尝试将其移动到任一可用的桌面;如果没有牌堆可以接受该纸牌,再尝试将

35、其移动到任一可用的桌面;如果没有牌堆可以接受该纸牌,则该纸牌将保留于原位置。最后,显示该牌堆所有下面的牌。则该纸牌将保留于原位置。最后,显示该牌堆所有下面的牌。z由于我们必须从上到下处理各个纸牌,这与列举堆栈元素的顺序刚好由于我们必须从上到下处理各个纸牌,这与列举堆栈元素的顺序刚好相反,因此,为了现这些,必须使用数组来实现堆栈。相反,因此,为了现这些,必须使用数组来实现堆栈。9-102tableau Piles 9-103Application Class z静态静态 main过程将会创建过程将会创建 一个应用类的实例一个应用类的实例. 应用构造函数 通过构建一个内嵌类SolitareFram

36、e 的实例创建了窗口应用。z显示窗口显示窗口z不同类的变量在类中共享,不同类的变量在类中共享, 因此被声明为因此被声明为 static data fields. 由方法由方法init实现数据的初始化。实现数据的初始化。z尽管实际游戏所使用的牌堆多种多样,需要表示各种尽管实际游戏所使用的牌堆多种多样,需要表示各种不同类型牌堆的变量值,但是游戏开发人员还是采用不同类型牌堆的变量值,但是游戏开发人员还是采用数组来存储各种不同类型的牌堆,并将数组元素声明数组来存储各种不同类型的牌堆,并将数组元素声明为为 CardPile 类型。类型。9-104Application Class 9-105Buildi

37、ng a More Complete Game9-106好的设计原则和度量标准 面向对象程序设计的度量标准:面向对象程序设计的度量标准:z可扩展性可扩展性:容易增加新的功能z灵活性灵活性:代码修改平稳地发生z可插入性可插入性:容易将一个类抽出去,同时将另一个有同样接口的类加入进来。面向对象程序设计是对数据的封装;范式(模板)的程序设计是对算法的封装。什么是面向对象程序设计?什么是面向对象程序设计?9-107面向对象的设计原则 “开闭”原则(the open-closed principle)任何系统在其生命周期中都会发生变化,如果我们希望开发出的系统不会在第一版后就被抛弃,那么我们就必须牢牢记

38、住这一点。定义定义:软件组成实体(类,模块,函数,等等)应该是可扩展的,但是不可修改的。9-108是否自相矛盾?是否自相矛盾?面向对象的设计原则开闭原则9-109例子:玉帝招安美猴王例子:玉帝招安美猴王当年大闹天宫便是美猴王对玉帝的新挑战。美猴王说:当年大闹天宫便是美猴王对玉帝的新挑战。美猴王说:皇帝皇帝轮流做,明年到我家。轮流做,明年到我家。只教他搬出去,将天宫让于我!只教他搬出去,将天宫让于我!对于对于这项挑战,太白金星给玉皇大帝提出的建议是:这项挑战,太白金星给玉皇大帝提出的建议是:降一道招安圣降一道招安圣旨,宣上界来旨,宣上界来,一则不劳师动众,二则收仙有道也。,一则不劳师动众,二则收

39、仙有道也。换而言之,不劳师动众、不破坏天规便是换而言之,不劳师动众、不破坏天规便是闭闭,收仙有道便是,收仙有道便是开开。招安之道便是玉帝天庭的。招安之道便是玉帝天庭的开放封闭开放封闭原则。原则。面向对象的设计原则开闭原则9-110模块可以操作一个抽象体。由于模块依赖于一个固模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改(定的抽象体,因此它可以是不允许修改(closed for modification)的;同时,通过从这个抽象体派生,)的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。也可扩展此模块的行为功能。符合符合OCP原则的程序只通过增加代码来变化

40、而不是原则的程序只通过增加代码来变化而不是通过更改现有代码来变化通过更改现有代码来变化,因此这样的程序就不会,因此这样的程序就不会引起象非开放引起象非开放封闭(封闭(open-closed)的程序那样)的程序那样的连锁反应的变化。的连锁反应的变化。面向对象的设计原则开闭原则9-111对可变性的封装 1.考虑系统中什么可能会发生变化考虑系统中什么可能会发生变化2.一种可变性不应当散落在代码的很多角落里,而一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里应当被封装到一个对象里3. 正确理解继承正确理解继承!4.3. 一种可变性不应当与另一个可变性混合在一起一种可变性不应当与另一个可

41、变性混合在一起9-112选择性的封闭(Strategic Closure) 没有任何一个大的程序能够做到没有任何一个大的程序能够做到100%的封的封闭。一般来讲,无论模块是多么的闭。一般来讲,无论模块是多么的“封闭封闭”,都会存在一些无法对之封闭的变化。既然,都会存在一些无法对之封闭的变化。既然不可能完全封闭,因此就必须选择性地对待不可能完全封闭,因此就必须选择性地对待这个问题。也就是说,设计者必须对于他这个问题。也就是说,设计者必须对于他(她)设计的模块应该对何种变化封闭做出(她)设计的模块应该对何种变化封闭做出选择。选择。 9-1139-1149-115两个原则两个原则-Liskov替换原

42、则(替换原则(LSP)描述:描述:若对每个类型若对每个类型S的对象的对象O1,都存在一个类型,都存在一个类型T的对象的对象O2,使得在所有针对,使得在所有针对T编写的程序编写的程序P中,用中,用O1替换替换O2后,程序后,程序P行为功能不变,则行为功能不变,则S是是T的子类型。的子类型。原则:原则:主要针对继承的设计原则主要针对继承的设计原则所有派生类的行为功能必须和客户程序对其基类所期望的保所有派生类的行为功能必须和客户程序对其基类所期望的保持一致。持一致。派生类必须满足基类和客户程序的约定派生类必须满足基类和客户程序的约定IS-A是关于行为方式的,行为方式不是内在的,私有的,而是关于行为方

43、式的,行为方式不是内在的,私有的,而是外在的,公开的。依赖客户程序的调用方式是外在的,公开的。依赖客户程序的调用方式 9-116两个原则两个原则-Liskov替换原则(替换原则(LSP)应用应用:在实现继承时,子类型(:在实现继承时,子类型(subtype)必须能替换掉它们)必须能替换掉它们的基类型(的基类型(base type)。如果一个软件实体使用的是基类的)。如果一个软件实体使用的是基类的话那么也一定适用于子类。但反过来的代换不成立。话那么也一定适用于子类。但反过来的代换不成立。个人观点:个人观点: LSP是使是使OCP成为可能的主要原则之一,对成为可能的主要原则之一,对LSP的的违反将

44、导致对违反将导致对OCP的违反,同时二者是的违反,同时二者是OOD中抽象和多态的理中抽象和多态的理论基础,在论基础,在OOPL中表现为继承。在高级语言(中表现为继承。在高级语言(JAVA、C#)中,只要我们严格按照接口和虚拟类的语法规范来做就能很好中,只要我们严格按照接口和虚拟类的语法规范来做就能很好遵循此原则,另外我们还应该避免一些更微妙的违规情况。遵循此原则,另外我们还应该避免一些更微妙的违规情况。例子:例子:正方形和矩形,矩形可以做为正方形的基类,因为正方正方形和矩形,矩形可以做为正方形的基类,因为正方形也是一种矩形,但对于正方形来说,形也是一种矩形,但对于正方形来说,setWidth(

45、)和和setHeight()是冗余的,且容易引起错误,这样的设计就违反了是冗余的,且容易引起错误,这样的设计就违反了LSP原则。原则。9-117一个违反LSP的简单例子-c# 正方形不能作为长方形的子类正方形不能作为长方形的子类9-118两个原则两个原则-Liskov替换原则(替换原则(LSP)解决策略解决策略:如果有两个具体类:如果有两个具体类A和和B之间的关系违反了之间的关系违反了LSP,可,可以在以下两种重构方案中选择一种:以在以下两种重构方案中选择一种:1 .创建一个新的抽象类创建一个新的抽象类C,作为两个具体类的超类,将作为两个具体类的超类,将A和和B共共同的行为移动到同的行为移动到

46、C中,从而解决中,从而解决A和和B行为不完全一致的问题。行为不完全一致的问题。2 .从从B到到A的继承关系改写为委派关系。的继承关系改写为委派关系。 9-119代码重构代码重构 9-120两个原则两个原则-合成、聚合复用原则合成、聚合复用原则 描述:描述:合成、聚合复用原则就是在一个新的对象里面使用一些合成、聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部份,新的对象通过向这些已有的对象,使之成为新对象的一部份,新的对象通过向这些对象的委派达到复用已有功能的目的。这个原则有一个简短的对象的委派达到复用已有功能的目的。这个原则有一个简短的描述:要尽量使用合成、聚合,描述

47、:要尽量使用合成、聚合,尽量不要使用继承尽量不要使用继承。 合成、聚合有如下好处合成、聚合有如下好处 :新对象存取成分对象的唯一方法是通过成分对象的接口。新对象存取成分对象的唯一方法是通过成分对象的接口。这种复用是黑箱复用,因为成分对象的内部细节是新对象所这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不到的。看不到的。这种复用可以在运行时间内动态进行,新对象可以动态的引这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。用与成分对象类型相同的对象。合成、聚合可以应用到任何环境中去,而继承只能应用到一合成、聚合可以应用到任何环境中去,而继承只能应用到一些有限环

48、境中去。些有限环境中去。导致错误的使用合成、聚合与继承的一个常见原因是错误的导致错误的使用合成、聚合与继承的一个常见原因是错误的把把“Has-a”关系当作关系当作“Is-a”关系。如果两个类是关系。如果两个类是“Has-a”关系关系那么应使用合成、聚合,如果是那么应使用合成、聚合,如果是“Is-a”关系那么可使用继承。关系那么可使用继承。9-121两个原则两个原则-合成、聚合复用原则合成、聚合复用原则 9-122两个原则两个原则-合成、聚合复用原则合成、聚合复用原则 9-123两个原则两个原则-合成、聚合复用原则合成、聚合复用原则 9-124两个原则两个原则-合成、聚合复用原则合成、聚合复用原则 9-126小 结软件的可复用性是度量软件质量的重要因素,继承是实现代码复用的有效手段,它将问题域中的泛化关系映射到解空间中。继承作为类型机制时,它定义了子类型关系:子类的对象实例中含有父类类型的子对象。多继承有其应用背景,但可能引起语法与语义的冲突,特别是考虑到重复继承的现象。C+的虚基类,对象模型,继承相关的两个设计原则9-127作 业

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号