《[工学]《C面向对象程序设计》第7章:多态性课件.ppt》由会员分享,可在线阅读,更多相关《[工学]《C面向对象程序设计》第7章:多态性课件.ppt(30页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第7章章 多态性多态性主要内容与学习目标主要内容与学习目标7.1 抽象方法与抽象类抽象方法与抽象类7.2 接口接口7.3 代理代理7.4 事件事件主要内容与学习目标主要内容与学习目标l主要内容:主要内容:(1)声明抽象类、声明抽象方法和属性。声明抽象类、声明抽象方法和属性。(2)实现抽象类、实现抽象方法和属性、抽象方法和属性的特点。实现抽象类、实现抽象方法和属性、抽象方法和属性的特点。(3)抽象类作为方法参数的类型。抽象类作为方法参数的类型。(4)从抽象类派生抽象类、抽象类的特点。从抽象类派生抽象类、抽象类的特点。(5)抽象类变量、动态绑定。抽象类变量、动态绑定。(6)声明接口及接口成员。声
2、明接口及接口成员。(7)实现接口、实现接口方法、实现接口属性。实现接口、实现接口方法、实现接口属性。(8)接口的属性、基于接口的多态性。接口的属性、基于接口的多态性。(9)接口与抽象类的区别。接口与抽象类的区别。(10)抽象类实现接口、将接口方法映射到抽象方法上。抽象类实现接口、将接口方法映射到抽象方法上。(11)一个类实现多个接口。一个类实现多个接口。(12)声明代理、调用代理。声明代理、调用代理。(13)代理的作用。代理的作用。(14)多路广播代理。多路广播代理。(15)声明事件的代理。声明事件的代理。(16)声明及使用自定义事件。声明及使用自定义事件。(17)定义及使用持有事件数据的类。
3、定义及使用持有事件数据的类。l学习目标:学习目标:(1)理解多态性是如何实现系统的可扩展和可维护的。理解多态性是如何实现系统的可扩展和可维护的。(2)理解抽象类和具体类之间的差异。理解抽象类和具体类之间的差异。(3)学会如何创建及使用接口和代理。学会如何创建及使用接口和代理。(4)理解事件与代理的关系及如何使用事件。理解事件与代理的关系及如何使用事件。7.1 抽象方法与抽象类抽象方法与抽象类l基类的虚拟方法有时候不可能被调用到,这时候可定义该虚拟方法为抽象方法,抽象方法没有方法体,但非声基类的虚拟方法有时候不可能被调用到,这时候可定义该虚拟方法为抽象方法,抽象方法没有方法体,但非声明不可,它告
4、诉编译器,派生类必须通过重写该方法以提供它们自己的实现。明不可,它告诉编译器,派生类必须通过重写该方法以提供它们自己的实现。l当实例方法声明包含当实例方法声明包含abstract修饰符时,称该方法为抽象方法。虽然抽象方法同时隐含为虚拟方法,但是它不修饰符时,称该方法为抽象方法。虽然抽象方法同时隐含为虚拟方法,但是它不能有能有virtual修饰符。修饰符。l在类声明中使用在类声明中使用abstract修饰符以指示类只能是其他类的基类,我们称它为抽象类。含有一个或一个以上的抽修饰符以指示类只能是其他类的基类,我们称它为抽象类。含有一个或一个以上的抽象成员的类,必须定义为抽象类。但必须注意抽象类可以
5、不包含抽象成员。象成员的类,必须定义为抽象类。但必须注意抽象类可以不包含抽象成员。l抽象类不能实例化,必须在继承类中实现。它可以包含已实现的方法和属性,但也可以包含未实现的方法和属抽象类不能实例化,必须在继承类中实现。它可以包含已实现的方法和属性,但也可以包含未实现的方法和属性,这些未实现方法和属性必须在继承类中实现。当需要一组相关组件来包含一组具相同功能的方法,但同时性,这些未实现方法和属性必须在继承类中实现。当需要一组相关组件来包含一组具相同功能的方法,但同时要求在其他方法实现中具有灵活性时,可以使用抽象类。抽象类的另一个好处是:当要求组件的新版本时,可要求在其他方法实现中具有灵活性时,可
6、以使用抽象类。抽象类的另一个好处是:当要求组件的新版本时,可根据需要将附加方法添加到基类,但抽象方法和属性必须保持不变。根据需要将附加方法添加到基类,但抽象方法和属性必须保持不变。l抽象类的目的是提供一个合适的基类,以派生其他的类。例如,我们可以有抽象类抽象类的目的是提供一个合适的基类,以派生其他的类。例如,我们可以有抽象类TwoDimensionalShape,并从该类派生诸如并从该类派生诸如Rectangle、Circle和和Ellipse等具体类。还可以有抽象类等具体类。还可以有抽象类ThreeDimensionalShape,并从该,并从该类派生诸如类派生诸如Cube、Sphere和和
7、Cylinder等具体类。等具体类。l继承层次结构并不一定需要包含抽象类。然而,却经常在类层次结构的顶部添加抽象类以减少客户代码对特定继承层次结构并不一定需要包含抽象类。然而,却经常在类层次结构的顶部添加抽象类以减少客户代码对特定子类类型的依赖。抽象类有时构成若干级别的层次结构。例如,子类类型的依赖。抽象类有时构成若干级别的层次结构。例如,图图7.1中的形状层次结构,该层次结构的顶部为中的形状层次结构,该层次结构的顶部为抽象类抽象类Shape。该层次结构的下一级是另外两个抽象类,即。该层次结构的下一级是另外两个抽象类,即TwoDimensionalShape和和ThreeDimensional
8、Shape。该层次结构的第三级为二维形状声明了具体类。该层次结构的第三级为二维形状声明了具体类(Rectangle、Circle和和Ellipse),也,也为三维形状声明了具体类为三维形状声明了具体类(Cube、Sphere和和Cylinder)。注意,图中抽象类的名称以斜体字出现。注意,图中抽象类的名称以斜体字出现。l7.1.1 抽象方法和抽象属性抽象方法和抽象属性l7.1.2 抽象类继承抽象类继承图7.1 Shape一般化/特殊化层次结构7.1.1 抽象方法和抽象属性抽象方法和抽象属性l抽象方法或属性是方法或属性声明中包含抽象方法或属性是方法或属性声明中包含abstract修饰符的方法或属
9、性,派生类必修饰符的方法或属性,派生类必须重写该方法或属性。注意抽象方法或属性没有方法体或属性访问器。须重写该方法或属性。注意抽象方法或属性没有方法体或属性访问器。l任务任务7.1:多态性及实现:多态性及实现(一一)问题描述:定义一抽象类问题描述:定义一抽象类Shape。抽象类。抽象类Shape包含包含x和和y两个属性、两个属性、Volume虚拟方法、虚拟方法、Area抽象方法和只读抽象方法和只读Name抽象属性。不同的形状类按继承关系建立抽象属性。不同的形状类按继承关系建立如如Point类实现类实现 Shape类;类;Rectangle类继承类继承Point类;类;Ellipse类继承类继承
10、Rectangle类;类;Circle类继承类继承Ellipse类;类;Cylinder类继承类继承Circle类。创建每个类的实例,并将每个类的实例存放于类类。创建每个类的实例,并将每个类的实例存放于类型为型为Shape的数组中。以该的数组中。以该Shape的数组作为参数,调用参数的类型为的数组作为参数,调用参数的类型为Shape数组的数组的ShowShapinfo方法,通过调用重写的方法和属性为相应的图形对象计算表面积,体积方法,通过调用重写的方法和属性为相应的图形对象计算表面积,体积并输出图形的名称。并输出图形的名称。本示例的继承结构如同任务本示例的继承结构如同任务6.8。只是本示例的层
11、次结构开始于抽象。只是本示例的层次结构开始于抽象类类Shape。Shape类声明了该层次结构的类声明了该层次结构的“接口接口”即程序可用于调用所有即程序可用于调用所有Shape类类对象的一组方法。对象的一组方法。图图7.2中的类图开始于抽象类中的类图开始于抽象类Shape。在。在Shape类中,我们将类中,我们将Area方法方法声明为抽象方法,声明为抽象方法,Name属性声明为抽象属性,因为实现它们没有什么意义。我们将属性声明为抽象属性,因为实现它们没有什么意义。我们将Volume方法声明为虚拟方法且返回值为方法声明为虚拟方法且返回值为0,因为二维图形的面积为,因为二维图形的面积为0,且实现,
12、且实现Shape类的类的三维图形必须重写三维图形必须重写Volume方法。方法。分析:分析:Point类实现类实现 Shape类,类,Point类继承类继承Shape类的类的 x 和和 y属性、属性、Volume虚拟方法,虚拟方法,实现实现Shape类的类的Area抽象方法和只读抽象方法和只读Name抽象属性,抽象属性,Area方法的返回值应为方法的返回值应为0,Name属性的值应为属性的值应为“点点”。Rectangle类继承类继承Point,Rectangle类有自己的两个属性类有自己的两个属性Height和和Width。Rectangle类必须重写类必须重写Area方法和方法和Name属
13、性。属性。Ellipse类继承类继承Rectangle类,类,Circle类继承类继承Ellipse类,类,Cylinder类从类从Circle类继承而来,类继承而来,Cylinder类从类从Circle类继承了类继承了方法方法Area及属性及属性Name和方法和方法Volume。与圆形相比,圆柱体的表面积和体积计算方法有。与圆形相比,圆柱体的表面积和体积计算方法有所不同,名称也不同,因此所不同,名称也不同,因此Cylinder类必须重写这些方法和属性。类必须重写这些方法和属性。图7.2 Shape层次结构的类图7.1.2 抽象类继承抽象类继承l在类声明中使用在类声明中使用MustInheri
14、t修饰符以指示类只能是其他类的基类,我们称它为修饰符以指示类只能是其他类的基类,我们称它为抽象类。含有一个或一个以上的抽象成员的类,必须定义为抽象类。但抽象类不抽象类。含有一个或一个以上的抽象成员的类,必须定义为抽象类。但抽象类不一定包含抽象成员。抽象类可以完全实现,但更常见的是部分实现或者根本不实一定包含抽象成员。抽象类可以完全实现,但更常见的是部分实现或者根本不实现,从而封装继承类的通用功能。抽象类可以继承其他抽象类,当然,抽象类可现,从而封装继承类的通用功能。抽象类可以继承其他抽象类,当然,抽象类可以继承其他非抽象类,在实际中,抽象类继承非抽象类很少见。以继承其他非抽象类,在实际中,抽象
15、类继承非抽象类很少见。l任务任务7.2:多态性及实现:多态性及实现(二二)问题描述:问题描述:声明声明图图7.1中除中除Ellipse和和Sphere外的每个类,每个外的每个类,每个TwoDimensionalShape类都应包含一个类都应包含一个Area方法,用以计算二维图形的面积,每个方法,用以计算二维图形的面积,每个ThreeDimensionalShape都应包含都应包含Area方法和方法和Volume方法,分别用于计算三维图形方法,分别用于计算三维图形的表面积和体积。创建一个程序,使用一个的表面积和体积。创建一个程序,使用一个Shape数组。其元素保存层次结构中每个具数组。其元素保存
16、层次结构中每个具体类的对象的引用,该程序应打印每个数组元素所引用数组的对象。此外,在处理数组体类的对象的引用,该程序应打印每个数组元素所引用数组的对象。此外,在处理数组的所有图形的循环中,判断每个图形是的所有图形的循环中,判断每个图形是TwoDimensionalShape还是还是ThreeDimensionalShape。如果图形是。如果图形是TwoDimensionalShape,则显示其面积和周,则显示其面积和周长。如果是长。如果是ThreeDimensionalShape,则显示其表面积和体积。,则显示其表面积和体积。解决方案:解决方案:l(1)创建一空项目创建一空项目Abstract
17、Class。向项目中添加代码文件:。向项目中添加代码文件:l(2)添加引用添加引用System.Windows.Forms,并将输出类型设置为,并将输出类型设置为【Windows应用程序应用程序】。l(3)按按F5键编译并运行应用程序,输出结果键编译并运行应用程序,输出结果如图如图7.5所示所示。图7.5 抽象类继承7.2 接口接口l在在C#中类只能实现中类只能实现“单一继承单一继承”,要实现多重继承,必须使用接口。也就是说,类,要实现多重继承,必须使用接口。也就是说,类可以实现无限多个接口,但仅能从一个抽象可以实现无限多个接口,但仅能从一个抽象(或任何其他类或任何其他类)类继承。从抽象类派生
18、类继承。从抽象类派生的类仍可实现接口。的类仍可实现接口。l一个接口定义一个协定,实现接口的类或结构必须遵守其协定。接口可以从多个基一个接口定义一个协定,实现接口的类或结构必须遵守其协定。接口可以从多个基接口继承,而类或结构可以实现多个接口。接口声明可以声明零个或多个成员,接接口继承,而类或结构可以实现多个接口。接口声明可以声明零个或多个成员,接口的成员必须是方法、属性、事件或索引器。接口不能包含常数、字段、运算符、口的成员必须是方法、属性、事件或索引器。接口不能包含常数、字段、运算符、实例构造函数、析构函数或类型,也不能包含任何种类的静态成员。接口本身不提实例构造函数、析构函数或类型,也不能包
19、含任何种类的静态成员。接口本身不提供它所定义的成员的实现,接口只指定实现该接口的类或接口必须提供的成员。供它所定义的成员的实现,接口只指定实现该接口的类或接口必须提供的成员。l所有接口成员都隐式地具有所有接口成员都隐式地具有public访问权限。接口成员声明包含任何修饰符都将导访问权限。接口成员声明包含任何修饰符都将导致编译时错误。具体地说,接口成员包含下列任何修饰符时都会出现编译时错误:致编译时错误。具体地说,接口成员包含下列任何修饰符时都会出现编译时错误:abstract、public、protected、internal、private、virtual、override、static。7
20、.2.1 声明和实现接口声明和实现接口7.2.2 接口成员的显式实现接口成员的显式实现7.2.3 接口和抽象类接口和抽象类7.2.4 接口与抽象类的比较接口与抽象类的比较7.2.1 声明和实现接口声明和实现接口l理这些完全不同类的对象。例如,分别代表经理、温度、汽艇的类的对象的功能完全不同,但这些类的对象理这些完全不同类的对象。例如,分别代表经理、温度、汽艇的类的对象的功能完全不同,但这些类的对象有一些通用的属性和操作,那就是它们所属的类别和对自己的描述。一个温度类的对象有一些通用的属性和操作,那就是它们所属的类别和对自己的描述。一个温度类的对象32,它所属的类别,它所属的类别为温度,其描述为
21、为温度,其描述为“华氏温度值:华氏温度值:32,摄氏温度值:,摄氏温度值:0”。一个经理类的对象王非,其描述为。一个经理类的对象王非,其描述为“姓名:王姓名:王非,基本工资:非,基本工资:8000”。下面我们声明一接口。下面我们声明一接口IThing来提供这些通用的功能,然后声明来提供这些通用的功能,然后声明Temperature类,类,Manager类,这些类都实现接口类,这些类都实现接口IThing。l任务任务7.3:创建和使用接口:创建和使用接口问题描述:问题描述:l(1)创建接口。创建一名为创建接口。创建一名为IThing的接口,该接口为所有对象提供通用功能,它有两个成员:只的接口,该
22、接口为所有对象提供通用功能,它有两个成员:只读读Type属性和属性和TellAboutSelf方法,方法,Type属性返回对象的所属类别,属性返回对象的所属类别,TellAboutSelf方法返回对方法返回对象的自描述。象的自描述。l(2)实现接口。创建实现接口。创建Temperature(温度温度)类,该类实现类,该类实现IThing接口。接口。Temperature类还有两个属类还有两个属性:华氏温度性:华氏温度(Value)和摄氏温度和摄氏温度(Celsius),华氏温度和摄氏温度的关系为:,华氏温度和摄氏温度的关系为:Value=9/5*Celsius+32。创建。创建Manager(
23、经理经理)类,该类实现类,该类实现IThing接口,接口,Manager类还有类还有3个个属性:属性:Name(姓名姓名)、Salary(基本工资基本工资)、BornDay(出生日期出生日期)。BornDay的类型为的类型为DateTime类型。类型。l(3)创建测试程序,该程序创建一个创建测试程序,该程序创建一个IThing变量数组,该数组存储实现接口的各个具体类的对象变量数组,该数组存储实现接口的各个具体类的对象的引用,利用循环结构,打印每个数组元素所引用对象的信息。的引用,利用循环结构,打印每个数组元素所引用对象的信息。解决方案:解决方案:l(1)创建一空项目创建一空项目Interfac
24、eApp。l(2)声明接口声明接口IThing。向该项目中添加代码文件。向该项目中添加代码文件l(3)实现接口实现接口IThing。再向该项目中添加代码文件。再向该项目中添加代码文件ImplementsInterface.cs:l(4)向项目中添加测试类向项目中添加测试类l(5)添加引用添加引用System.Windows.Forms,并将输出类型设置为,并将输出类型设置为【Windows应用程序应用程序】。l(6)按按F5键编译并运行应用程序,输出结果键编译并运行应用程序,输出结果如图如图7.6所示所示。图7.6 不同类的对象的多态演示7.2.2 接口成员的显式实现接口成员的显式实现l如果两
25、个接口具有相同的成员名称,实现接口的类为每个接口成员提供一个单独的实现,这如果两个接口具有相同的成员名称,实现接口的类为每个接口成员提供一个单独的实现,这时必须采用接口成员的显式实现。接口成员的显式实现是指实现接口的成员时成员名称使用时必须采用接口成员的显式实现。接口成员的显式实现是指实现接口的成员时成员名称使用完全限定接口成员名。完全限定接口成员名包含声明该成员的接口的名称,后跟一个点,再完全限定接口成员名。完全限定接口成员名包含声明该成员的接口的名称,后跟一个点,再后跟该成员的名称。后跟该成员的名称。l任务任务7.4:用接口实现不同的度量衡系统:用接口实现不同的度量衡系统 问题描述:声明两
26、个接口问题描述:声明两个接口IEnglishDimensions和和IMetricDimensions。它们有相同。它们有相同Length和和Width属性且属性名相同,它们表示不同的度量衡系统,实现属性且属性名相同,它们表示不同的度量衡系统,实现IEnglishDimensions接口的类,实现接口属接口的类,实现接口属性的值的单位是英制单位,而实现性的值的单位是英制单位,而实现IMetricDimensions接口的类,实现接口属性的值的单位是公制单接口的类,实现接口属性的值的单位是公制单位,声明一位,声明一Box 类,它有两个字段类,它有两个字段length和和width,分别存储,分别
27、存储Box的长和宽的长和宽(单位是厘米单位是厘米),Box类实类实现现IEnglishDimensions和和IMetricDimensions两个接口,创建两个接口,创建Box对象,同时以公制单位和英制单位对象,同时以公制单位和英制单位显示该显示该Box对象的尺寸。对象的尺寸。分析:分析:IEnglishDimensions和和IMetricDimensions两个接口表示不同的度量衡系统,两个接口有相两个接口表示不同的度量衡系统,两个接口有相同的成员名同的成员名Length和和Width。Box类实现类实现IEnglishDimensions和和 IMetricDimensions两个接口
28、,并两个接口,并为每个接口成员提供一个单独的实现。为每个接口成员提供一个单独的实现。解决方案:解决方案:(1)创建一空项目创建一空项目TestInterface。向该项目中添加代码文件:。向该项目中添加代码文件:(2)添加引用添加引用System.Windows.Forms。将输出类型设置为。将输出类型设置为【Windows输出类型输出类型】。(3)按按F5键编译并运行应用程序,输出结果键编译并运行应用程序,输出结果如图如图7.7所示所示。图7.7 运行结果7.2.3 接口和抽象类接口和抽象类l而必须从中继承的类。抽象类可以完全实现,但更常见的是部分实现或者根本不实现。而必须从中继承的类。抽象
29、类可以完全实现,但更常见的是部分实现或者根本不实现。l因此抽象类可封装继承类的通用不变的功能,但也可通过实现抽象类的抽象方法和属性为继承类提供其不同因此抽象类可封装继承类的通用不变的功能,但也可通过实现抽象类的抽象方法和属性为继承类提供其不同的功能。相反,的功能。相反,“接口接口”是完全抽象的成员集合,可以被看作是为操作定义合同。接口的实现完全留给开发是完全抽象的成员集合,可以被看作是为操作定义合同。接口的实现完全留给开发者去做。者去做。l任务任务7.5:薪水发放系统:薪水发放系统问题描述:问题描述:某公司有各类员工,如销售员、计时工人、计件工人、经理,他们的薪水计算方法不同,某公司有各类员工
30、,如销售员、计时工人、计件工人、经理,他们的薪水计算方法不同,各类员工的薪水计算方法如下:各类员工的薪水计算方法如下:l经理:固定月薪加奖金。经理:固定月薪加奖金。l销售员:固定月薪加销售提成销售员:固定月薪加销售提成(月薪月薪+销售额销售额*提成率提成率)。l计时工人:计时工资加加班费计时工人:计时工资加加班费(每小时工资每小时工资*工作小时数工作小时数+加班小时加班小时*1.5每小时酬金每小时酬金)(每周超过每周超过40小时,就算加班小时,就算加班)。l计件工人:计件工资计件工人:计件工资(计件工资计件工资*生产的产品件数生产的产品件数)。l试设计一抽象类试设计一抽象类Employee(雇
31、员雇员),该抽象类实现上例中声明的,该抽象类实现上例中声明的IThing接口,实现接口,实现Employee抽抽象类,计算各类员工的薪水。象类,计算各类员工的薪水。分析:每一对象都有其所属的类别和自描述,公司员工也不例外,公司员工类可实现分析:每一对象都有其所属的类别和自描述,公司员工也不例外,公司员工类可实现IThing接口。对接口。对于公司的每一员工来说,都有姓名、工资、出生日期,但对不同类型的员工,薪水的计算方法不同。于公司的每一员工来说,都有姓名、工资、出生日期,但对不同类型的员工,薪水的计算方法不同。因此我们定义一因此我们定义一Employee抽象类,该抽象类提供一组实现的成员抽象类
32、,该抽象类提供一组实现的成员(如如Name(姓名姓名)、Salary(工资工资)、BornDay(出生日期属性出生日期属性)、TellAboutSelf方法等方法等)为公司的各类员工提供通用不变的功能,提供一组为公司的各类员工提供通用不变的功能,提供一组抽象成员抽象成员(如如Earnings方法方法(计算实发工资计算实发工资)、Type属性属性),以为公司的各类员工对象能够实现其不同,以为公司的各类员工对象能够实现其不同的功能。由于不同类型的员工都有其特有属性,因此我们在的功能。由于不同类型的员工都有其特有属性,因此我们在Employee类中,将类中,将TellAboutSelf方法声方法声明
33、为可重写方法,每个派生类都可重写该方法明为可重写方法,每个派生类都可重写该方法。图。图7.8中的类图显示了薪水发放应用程序的继承层次结中的类图显示了薪水发放应用程序的继承层次结构。构。解决方案:解决方案:l(1)打开打开“任务任务7.3:创建和使用接口:创建和使用接口”中创建的项目中创建的项目InterfaceApp。l(2)将该项目中的将该项目中的ImplementsInterface.cs代码文件的代码全部删除,然后添加程序代码代码文件的代码全部删除,然后添加程序代码l(3)按按F5键编译并运行应用程序,输出结果键编译并运行应用程序,输出结果如图如图7.9所示所示。图7.8 Employe
34、e层次结构图7.9 抽象类与接口7.2.4 接口与抽象类的比较接口与抽象类的比较l如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类可以提供已实现如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。抽象类可以提供已实现的成员。因此,可以用抽象类确保特定数量的相同功能,抽象类允许部分实现类,而接口不的成员。因此,可以用抽象类确保特定数量的相同功能,抽象类允许部分实现类,而接口不包含任何成员的实现。包含任何成员的实现。l抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用功能(如例中的如例中的I
35、Thing接口接口)。l组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而系的方法、属性组成。通过实现接口,组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰核心功能。功能。l虽然接口实现可以进化,但接口本身一旦被发布就不能再更改。对已发布的接口进行更改会虽
36、然接口实现可以进化,但接口本身一旦被发布就不能再更改。对已发布的接口进行更改会破坏现有的代码。若把接口视为约定,很明显约定双方都各有其承担的义务。接口的发布者破坏现有的代码。若把接口视为约定,很明显约定双方都各有其承担的义务。接口的发布者同意不再更改该接口,接口的实现者则同意严格按设计来实现接口。同意不再更改该接口,接口的实现者则同意严格按设计来实现接口。l从版本控制的角度来看,抽象类比接口更为灵活。使用类,可以发布版本从版本控制的角度来看,抽象类比接口更为灵活。使用类,可以发布版本1.0,然后在版本,然后在版本2.0中把新方法添加到该类。只要方法不是抽象的,任何现有派生类便继续运行,而不会发
37、中把新方法添加到该类。只要方法不是抽象的,任何现有派生类便继续运行,而不会发生更改。生更改。l由于接口不支持实现继承,适用于类的模式不适用于接口。将方法添加到接口等效于将抽象由于接口不支持实现继承,适用于类的模式不适用于接口。将方法添加到接口等效于将抽象方法添加到基类;实现该接口的任何类都将中断,原因是类没有实现新方法。方法添加到基类;实现该接口的任何类都将中断,原因是类没有实现新方法。7.3 代理代理l在多数情况下,调用方法时,我们指定要直接调用该方法。如果在多数情况下,调用方法时,我们指定要直接调用该方法。如果MyClass类有名为类有名为 Process的方法,我们通常的方法,我们通常这
38、样调用它:这样调用它:MyClass myClass=new MyClass();myClass.Process();l然而,有时我们并不想直接调用一个方法然而,有时我们并不想直接调用一个方法我们希望能够将它作为参数传给其他方法以便其他方法可以调用我们希望能够将它作为参数传给其他方法以便其他方法可以调用它。它。l虽然虽然C#不允许将方法引用作为参数直接传给其他方法,但它提供了封装有一系列方法引用的特殊类,名为代理。不允许将方法引用作为参数直接传给其他方法,但它提供了封装有一系列方法引用的特殊类,名为代理。包含方法引用的代理可传递给另一方法。此时不是直接发送一个方法的引用,相反对象是发送代理实例
39、,其中包含方法引用的代理可传递给另一方法。此时不是直接发送一个方法的引用,相反对象是发送代理实例,其中包含了我们希望发送的方法引用。然后接收代理引用的方法可调用代理中包含的方法。包含了我们希望发送的方法引用。然后接收代理引用的方法可调用代理中包含的方法。l要使用代理,首先必须声明一个。代理声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于要使用代理,首先必须声明一个。代理声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于静态方法,代理对象封装要调用的方法。对于实例方法,代理对象同时封装一个实例和该实例上的一个方法。静态方法,代理对象封装要调用的方法。对于实例方法,代理对
40、象同时封装一个实例和该实例上的一个方法。如果您有一个代理对象和一组适当的参数,则可以用这些参数调用该代理。如果您有一个代理对象和一组适当的参数,则可以用这些参数调用该代理。l代理的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数代理的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与代理的参数类型和返回类型相匹配。这使得代理完全适合类型和返回类型必须与代理的参数类型和返回类型相匹配。这使得代理完全适合“匿名匿名”调用。调用。7.3.1 使用代理使用代理7.3.2 组合代理组合代理7.3.3 代
41、理应用举例代理应用举例7.3.1 使用代理使用代理l定义和使用代理分定义和使用代理分3个步骤:声明、实例化和调用。个步骤:声明、实例化和调用。l任务任务7.6:使用代理实现运算:使用代理实现运算 问题描述:输入问题描述:输入x、y两个数,编写一方法两个数,编写一方法Process,通过该方法的,通过该方法的一个参数引用代理对象,可求出一个参数引用代理对象,可求出x、y的和、差、积、商及其中的最的和、差、积、商及其中的最大值和最小值。大值和最小值。解决方案:解决方案:l(1)创建一个创建一个【Windows应用程序应用程序】项目项目DelegateClass。l(2)向向DelegateClas
42、s项目中添加代码文件项目中添加代码文件l(3)选择窗体,向窗体添加选择窗体,向窗体添加4个文本框个文本框textBox1、textBox2、textBox3、textBox4和一个按钮控件和一个按钮控件button1,并将它们拖到适当的位置,并将它们拖到适当的位置(如图如图7.5所示所示)。将文本框的。将文本框的text属性设置为空,将属性设置为空,将textBox4的的ReadOnly属性设属性设置为置为“True”。将按钮控件。将按钮控件button1的的text属性设置为属性设置为“调用代理调用代理”l(4)双击双击button1按钮,向按钮,向button1_Click事件处理程序中添
43、加代码事件处理程序中添加代码l(5)按按F5键编译并运行应用程序,输入两个数及符号键编译并运行应用程序,输入两个数及符号“*”,单击,单击【调用调用代理代理】按钮,输出结果按钮,输出结果如图如图7.10所示所示图7.10 调用代理实现运算7.3.2 组合代理组合代理l在在C#中,代理为多路广播,这意味着它们可以同时指向多个方法。多路广播代理维护一个在调用中,代理为多路广播,这意味着它们可以同时指向多个方法。多路广播代理维护一个在调用该代理时将全部被调用的函数的列表。您可以使用该代理时将全部被调用的函数的列表。您可以使用“+=”运算符来合并两个代理。例如:运算符来合并两个代理。例如:Delega
44、teClass2.fun d1=new DelegateClass2.fun(add);d1+=new DelegateClass2.fun(max);d1+=new DelegateClass2.fun(divide);这时这时d1包含包含3个代理。个代理。若要从多路广播代理中移去一个代理,使用若要从多路广播代理中移去一个代理,使用“-=”运算符。例如:运算符。例如:d1-=new DelegateClass2.fun(add);这时代理这时代理DelegateClass2.fun(add)从多路广播代理中移去。从多路广播代理中移去。l当您调用多路广播代理时,调用列表中的代理将被按它们出现的
45、顺序同步调用。如果在此进程中当您调用多路广播代理时,调用列表中的代理将被按它们出现的顺序同步调用。如果在此进程中发生错误,执行进程将被中断。发生错误,执行进程将被中断。注意:只可组合相同类型的代理,并且代理类型必须具有注意:只可组合相同类型的代理,并且代理类型必须具有void返回值。若不是返回值。若不是void返回值,则返回值是最返回值,则返回值是最后一个被调用的代理的返回值。后一个被调用的代理的返回值。l任务任务7.7:使用组合代理实现运算:使用组合代理实现运算问题描述:输入问题描述:输入x、y两个数,调用方法两个数,调用方法Process,一次求出,一次求出x、y的和、差、积、商及其中的最
46、大值和最小的和、差、积、商及其中的最大值和最小值。值。解决方案:解决方案:(1)修改修改DelegateClass1项目,删除项目,删除mathClass类,修改类,修改DelegateClass2类类(2)向向Form1窗体类添加一些方法窗体类添加一些方法(3)将将button1_Click事件处理程序修改事件处理程序修改(4)按按F5键编译并运行应用程序,输入两个数及组合符键编译并运行应用程序,输入两个数及组合符“combin”,单击,单击【调用代理调用代理】按钮,输出结果按钮,输出结果如图如图7.11所示。所示。图7.11 使用组合代理7.3.3 代理应用举例代理应用举例l任务任务7.8
47、:用代理排序数组:用代理排序数组问题描述:问题描述:我们要按升序和降序对一数组进行排序。要求不提供单独的升序和降序排序方法我们要按升序和降序对一数组进行排序。要求不提供单独的升序和降序排序方法(为每种为每种类型的比较都提供一个类型的比较都提供一个),而只提供一个方法,它接受一个参数,该参数引用了要使用的比较方法。为,而只提供一个方法,它接受一个参数,该参数引用了要使用的比较方法。为执行降序排序,向排序方法传递对降序排序比较方法的一个引用,为执行升序排序,向排序方法传递执行降序排序,向排序方法传递对降序排序比较方法的一个引用,为执行升序排序,向排序方法传递对升序排序比较方法的一个引用。然后排序方
48、法可以使用这个引用对数组进行排序对升序排序比较方法的一个引用。然后排序方法可以使用这个引用对数组进行排序排序方法无需排序方法无需知道自己执行的是升序还是降序排序。知道自己执行的是升序还是降序排序。解决方案:解决方案:l(1)创建窗体,编写代码。创建窗体,编写代码。创建一个新的创建一个新的Visual C#Windows应用程序项目应用程序项目Test Delegate,并创建名为,并创建名为Form1 的的窗体窗体 向向Form1中添加中添加3个按钮和两个文本框个按钮和两个文本框 如表如表7.1所示所示命名对象命名对象 在在【项目项目】菜单中选择菜单中选择【添加类添加类】命令,向项目中添加类命
49、令,向项目中添加类DelegateSort 将代码放置到将代码放置到Form1的字段声明部:的字段声明部:在在Form1类中声明类中声明3个方法个方法 将代码添加到将代码添加到createbutton_Click事件处理程序中事件处理程序中 将代码添加到将代码添加到descendingbutton_Click事件处理程序中事件处理程序中 将代码添加到将代码添加到ascendingbutton_Click事件处理程序中事件处理程序中l(2)测试程序。测试程序。测试程序,按测试程序,按F5键。键。先单击先单击【创建数组创建数组】按钮。然后单击按钮。然后单击【降序排列降序排列】按钮,结果按钮,结果如
50、图如图7.12左图所示左图所示。单击单击【升序排列升序排列】按钮,结果按钮,结果如图如图7.12右图所示右图所示。表7.1 Form1的对象属性图7.12 使用代理排序7.4 事件事件l事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互互(例如鼠标单击例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。引发引起的,也可能是由某些其他的程序逻辑触发的。引发(触发触发)事件的对象叫做事件发送方。捕获事件并对其作出响应的对象叫做事件的对象叫做事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。事件接收方。l在事