《用对象思考.ppt》由会员分享,可在线阅读,更多相关《用对象思考.ppt(38页珍藏版)》请在三一文库上搜索。
1、第7章 用对象思考,在前一章,学习了.NET中的类和对象,也会用面向对象的思想来编写程序了。在面向对象编程中经常会遇到对象与对象之间消息的传递。其实在现实世界中我们发现对象之间存在更多的关系,如亲人关系、朋友关系等。在OOP中,同样需要对象之间建立关系,实现更多通信。本章将深入OOP编程,深入学习面向对象编程的更多特性。本章主要涉及到的知识点有: 继承:实现代码的复用,建立类的层次关系。 多态:理解多态原理,用抽象类和抽象方法实现多态。 接口:理解接口的定义以及应用。 了解序列化和反序列化,程序集和反射。 了解C#面向对象特性,学会使用索引、委托和事件。,7.1 用对象思考:继承,现实中存在着
2、各种继承关系,比如动物与猫的关系、轿车与车的关系。在面向对象编程中也存在“继承”的概念,C#能够利用继承的强大机制,在面向对象编程中更好的模拟现实世界,实现更复杂的面向对象描述,实现软件中的代码复用。,7.1.1 解决代码的冗余,【本节示例参考:源代码chapter77.1.1InheritExample】 在完成一个信息管理系统的时候,笔者把系统的使用者分为两类,一般用户和管理员,他们具有不同的权限。通过类图对比一下这两个类,如图7.1所示。 可以看到,User类和Admin类都具有相同的属性Id和Name,具有相同的方法SayHi。也就是说对于系统的使用者,两个类都具有一些相同的状态和行为
3、。考虑到系统的扩展性,以后系统的使用者角色可能会更多,这会编写大量重复的代码,造成冗余。为了解决这个问题,下面重新创建一个新类Person,用于保存User类和Admin类公共的属性和方法,如图7.2所示。实现三个类的关系的程序如代码7-1所示。,7.1.2 建立继承模型,【本节示例参考:源代码chapter7 7.1.2InheritExample】 在面向对象编程中,什么是继承呢?在现实世界中,继承关系是到处存在的,如用户和管理员都是人,他们都具有人的共同特性;猫和狗都是动物,都具有动物的共同特性。我们把现实中可以通过“is a”描述的关系,称作继承关系。即“管理员是人(Admin is
4、a Person)”,“狗是动物(dog is animal)”。 C#中是如何实现继承的呢?通过“:”运算符。在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类(即基类)。例如下面的定义的类实现了“管理员是人”的关系: public class Person /Person类成员 public class Admin : Person /Admin类成员 ,7.1.3 继承隐私保护,【本节示例参考:源代码chapter7 7.1.3InheritExample】 继承可以实现不同范围的隐私保护,通过public、private、protected访问修饰符可以实现。它们的区别
5、如图7.3所示。下面通过提出问题,解决问题的方式学习继承的访问控制。 (1)如果父类中的成员,只允许父类自己所独有,不允许子类和其他类访问,如何实现?将父亲的成员声明为private的,该成员就只能被父类本身访问,如图7.4所示。 (2)如果父类中的成员,只允许父类和其子类访问,不允许其他类访问,如何实现将父亲的成员声明为protected的,该成员就只能被父类和其子类访问,如图7.5和图7.6所示。,7.1.4 继承的特性,继承实现了类与类之间的层次关系,描述了更加丰富多彩的面向对象世界。同时,继承也具有一些特点需要了解。 (1)传递性 在信息管理系统中,用户分为一般用户和管理员,因为后台管
6、理的需要,必须要有一个超级管理员的角色来实现整个系统的管理。这样子,系统的用户关系图如图7.7所示。在程序中的继承关系如图7.8所示。 通过这种传递的关系,SuperAdmin不仅可以共享父类Admin的公共属性和方法,同时可以共享Person类的属性和方法。 (2)单根性 在上一节已经提到了,C#中的继承是单继承的,不支持多继承。 (3)密封性 在C#中可以通过关键字sealed来修饰一个类,这样的类是不能被继承的,这种类称为密封类。,7.1.5 继承的价值,【本节示例参考:源代码chapter77.1.5InheritExample】 继承模拟了现实世界中的关系,在OOP中强调一切皆对象,
7、这符合面向对象的编程思想,同时,继承实现了代码的重用,减轻了程序员编写代码的负担。继承使程序的结构更加清晰,使类与类之间的关系层次机构化,更易于维护。合理地使用继承,会对程序编写带来很大帮助。最后,通过一个综合例子来完成对继承的学习。请看下面的描述: 狗是哺乳动物。狗通常的时候和人打招呼会通过摇摇尾巴,在被抚摸感到舒服的时候,会旺旺叫,而在受到惊吓情绪烦躁时,会发出呜呜声。青蛙不是哺乳动物,属于卵生。当青蛙情绪好的时候,会在岸边呱呱呱的唱歌,而在受到惊吓时,会扑通一声跳入水中。,7.2 用对象思考:多态,在前面,学习了面向对象的封装和继承特性,通过封装保证了数据的安全性,通过继承实现了代码的重
8、用。面向对象还有个非常重要的概念,那就是多态。本章将通过多个例子来详细讲解多态的概念。,7.2.1 巧妙解决继承带来的问题,【本节示例参考:源代码chapter77.2.1AbstractExample】 在管理系统中,用户和管理员都需要通过登录才能进入系统,按照继承的原理,应该将登录的公共方法放到父类当中,如图7.10所示。,7.2.2 用抽象类和抽象方法实现多态,【本节示例参考:源代码chapter77.2.1MultistateExample】 上一节就是用抽象类和抽象方法实现了多态,当用户和管理员调用Login()方法的时候,他们的响应是不一样的,也就是说用户和管理员具有多态性。在基类
9、Person中声明抽象方法Login(),让它约束子类,必须实现不同的Login(),从而实现多态。 (1)抽象方法是一个没有方法体的方法,用关键字abstract修饰。语法如下: abstract 返回类型 方法名(); 方法被标记为抽象的,要求子类必须实现该方法;方法没有方法体(方法名括号后直接跟一个分号结束),提醒编译器注意,如果子类没有重写该抽象方法,就会报错。 注意:抽象方法没有闭合的大括号,下面的声明方式是错误的: abstract 返回类型 方法名() (2)含有抽象方法的类被称为抽象类,用关键字abstract修饰。语法如下: abstract class 类名 抽象类中并不一
10、定都是抽象方法,抽象类中也可以容纳具体实现的方法。但是,有抽象方法的类必然是抽象类。 (3)在子类中,通过关键字override重写抽象方法,语法如下: override返回类型 方法名(),7.2.3 使用虚方法实现多态,【本节示例参考:源代码chapter77.2.3VirtualExample】 继续上一节的实例,在用户信息管理系统中,父类(Person)定义一个登录的抽象方法Login()。要求子类去实现它。但是通常情况下,许多角色的用户登录系统的逻辑是一样的,只是管理员(Admin)的有所差别。 那么这个通用的登录方法应该放在哪里呢?按照上面的思想,应该放在父类里面。我们希望这样实现
11、:“父类(Person)中的Login()方法实现通用的登录逻辑,一般用户(User)默认采用父类的登录方法即可,管理员(Admin)可以把父类的登录方法重写,实现管理员特有的登录逻辑”。可以实现吗? C#中用virtual方法修饰的方法称为虚方法,虚方法可以有具体的实现,也可以被重写。在父类中定义虚方法,然后在子类中可以重写虚方法,也实现了面向对象的多态。虚方法的语法如下: virtual 返回类型 方法名() /方法体 ,7.2.4 面向对象的三大特性,到此为止,我们已经学习了面向对象中的三个重要特性:封装、继承、多态。现总结一下: (1)封装:保证对象自身数据的完整性、安全性。 (2)继
12、承:建立类之间的关系,实现代码重用,方便系统扩展。 (3)多态:相同的方法调用可实现不同的实现方式。,7.3 用对象思考:接口,在面向对象的编程系统中,系统的各种功能都是由许许多多的对象协作完成的。各个对象之间的协作关系成为了系统设计的关键,现在非常流行的面向接口编程实现的就是这种思想。本章将学习面向对象的接口技术。,7.3.1 接口的提出,面向对象中的接口概念,同样源于现实世界。在现实世界中,接口就是一套规范,满足这个规范的设备,就可以将它们组装到一起,从而实现设备的功能。例如,国际上定义了许多螺丝螺母的标准,根据这些标准就可以生产各种型号的螺母以及配套的螺丝,如图7.11、图7.12所示。
13、满足标准的螺丝和螺母,可以用于制造机床、改装汽车等,只要它们提供通用的接口。,7.3.2 接口的定义,【本节示例参考:源代码chapter77.3.2InterfaceExample】 在.NET中,接口同样是一种标准和规范,它可以约束类的行为,使不同的类能达到一个统一的规范。接口中可以包含字段、属性、方法和索引器等。但是接口中的属性和方法都不能实现。这好比是更加抽象的抽象类。接口用interface关键字声明,语法如下: public interface IPerson int Name /接口中的属性 get; set; void SayHi(); /接口中的方法 ,7.3.3 接口的实现
14、,【本节示例参考:源代码chapter77.3.3InterfaceExample】 定义好接口以后,类就可以实现接口,实现接口的语法如下: class User : IPerson 如果一个类要继承一个父类,同时要实现多个接口,则以“,”号隔开,语法如下: class User : Person,IPerson,IComparable 下面,通过程序实例来学习接口。在用户管理信息系统中,要求每个用户登录,都要把登录的状态、时间写进日志。现定义一个接口ILog来完成写日志的操作,然后在User类和Admin类中分别实现这个接口。类之间的关系如图7.16所示。,7.4 老鼠的儿子会打洞,在7.2
15、.1节,遗留了一个问题,泛型集合List用于保存用户对象,为什么父类类型约束的泛型集合可以存储它的子类对象呢?原则上子类对象是可以赋给父类对象的,即子类可以完全替代父类,并且出现在父类能够出现的地方。但是反过来,父类对象是不能替换子类对象的。我们称这样的特性为“里氏替换原则(LSP)”,可以通俗地理解为“老鼠的儿子会打洞”。对于里氏替换原则有两个关键的技术,就是is操作符和as操作符。,7.4.1 is操作符,【本节示例参考:源代码chapter77.4.1IsExample】 is操作符用于检查对象是否与给定类型兼容,is英文单词的意思为“是”。可以用is来判断对象是否是给定的数据类型。例如
16、下面的语法: if(obj is Person) /执行相应的操作 定义下面这样一个程序结构,然后通过代码7-10,检查程序的结果。 abstract class Person /代码省略 class User : Person /代码省略 ,7.4.2 as操作符,【本节示例参考:源代码chapter77.4.2AsExample】 在讲as操作符之前,先了解一下强制类型转换。强制类型转换是通过“( )”运算符来实现的。如果转换不通过,则会引发异常。如图7.17演示了强制类型转换的用法。,7.5 序列化与反序列化,序列化是将对象的状态存储到特定存储介质中的过程,也可以说是将对象状态转换为可保
17、持或传输的过程。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。,7.5.1 序列化和反序列化的用途,对于一个Windows程序来说,配置文件是非常重要的,配置文件保存了系统的设置,用户可以通过采用修改配置信息的方式定制软件的特性和功能。如图7.18显示了Windows操作系统的壁纸显示设置。当设置好壁纸的显示信息以后,程序会根据用户的要求来显示壁纸。,7.5.2 序列化和反序列化操作,【本节示例参考:源代码chapter77.5.Bina
18、rySerializerExample】 .NET提供多种形式的序列化,文本、XML流等。目前使用二进制流方式对泛型支持得最好。二进制格式化器具有一个非常重要的方法:Serialize()。语法如下: public void Serizlize(String serializationStream, Object graph); 其中第一个参数指定序列化过程的文件流,第二个参数指定要保存的对象。序列化的基本操作流程如图7.19所示。,7.6 程序集和反射,程序集是.NET框架应用程序的主要构造块。它是一个功能集合,并以实现单个单元的形式生成、版本化和部署。.NET的应用程序由程序集(Assem
19、bly)、模块(Module)、类型(Class)几个部分组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息。本章将介绍程序集和反射的原理和应用。,7.6.1 什么是程序集,我们创建的控制台应用程序,在binDebug文件夹下都会生成一个.exe的文件,当需要运行程序的时候,双击.exe文件,程序会自动打开,实现所有功能。为什么运行这个文件就能实现这个功能,而无须打开开发环境呢?其实,这个编译好了的.exe文件就是程序集文件。程序集是.NET框架应用程序的生成块,它包含编译好的代码逻辑单元。 程序集由描述它的清单、类型元数据、MSIL代码和资源组成,这些部分都
20、分布在一个文件中,或者分布在多个文件中。一个项目对应一个程序集。项目名与程序集名相同。,7.6.2 查看程序集,程序生成的程序集如.exe、.dll文件等,是一个物理的逻辑单元。知道了程序集结构以后,如何查看程序集呢?.NET提供了一个简易的反编译工具ILDasm,专门用于查看IL汇编语言,也可以看到程序集的类以及类的成员等。通过下面的步骤查看程序集。 (1)单击“开始”“程序”“Microsoft Visual Studio 2008”“Visual Studio Tools”“Visual Studio 2008命令提示”命令。打开“命令提示”对话框。 (2)在对话框中输入“ILDasm”
21、,弹出“ILDasm程序”窗口,选择“打开”命令,打开一个.exe或者.dll文件,就能够将程序集中的内容显示出来,如图7.20所示。 打开程序集清单,就可以看到版本号。打开类的方法,就可以查看MSIL代码,如图7.21所示,为打开一个类的方法后展示的程序清单。同时,右击“.exe”格式的文件,也可以查看到相应的程序集属性信息,如图7.22所示。,7.6.3 反射,【本节示例参考:源代码chapter77.6.3RefectionExample】 反射在编程中经常用到,例如当在Visual Studio中输入一个类型,然后输入“.”运算符时,程序就会弹出一个列表,列出这个类型所能访问的属性、方
22、法、事件等,这就是反射机制。反射可以获取已加载的程序集和在其中定义的类型信息。也可以通过反射在运行时创建类型实例,以及调用和访问这些实例。 如上一节所示,通过ILDasm查看到的程序集信息,就是使用的反射机制。反射的一个主要功能就是查找程序集信息。反射主要通过下面的类实现。 System.Refection.Assembly类可以获得正在运行的装配件信息,也可以动态地加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。 System.Refection.Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等,通过Type类可以得到这些要素的信息,并且调用之。
23、System.Refection.MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。,7.7 像引用数组一样引用自己的类:索引器,索引器允许类或结构的实例按照与数组相同的方式进行索引。索引器是C#引入的特性,同类型的语言如Java和C+都没有这种特性。索引类似于属性,不同之处在于索引器采用参数。本章将介绍索引的定义和应用。,7.7.1 索引器的定义,【本节示例参考:源代码chapter77.7.1IndexExample】 C#提供了一个非常有用的工具,就是索引器。大家都知道通过数组可以保存一系列具有相同数据类型的数据,数组对这一系列数据的操作提
24、供了很好的支持。那么,在类中,可不可以也像引用数组一样引用自己的类呢?正好C#提供的索引器可以实现这一功能。 那么,如何定义一个索引器呢?索引器采用访问修饰符后跟返回值类型、this关键字、索引参数。其中索引参数包含在操作符“ ”中。,7.7.2 索引器的实例,【本节示例参考:源代码chapter77.7.2IndexTest】 最后,通过一个实例来学习索引器的应用。该实例通过索引器来访问User类的对象。,7.8 委托和事件,C#的一个重要特性之一是支持委托(Delegate)和事件(Event)。委托和事件这两个概念是完全配合的。委托可以理解为函数指针,那就是说,它能够引用函数,通过传递地
25、址的机制完成。事件借助委托的帮助,使用委托调用已订阅事件的对象中的方法。,7.8.1 委托的定义,在定义方法的时候,我们可以将基本数据类型作为方法参数,也可以将类实例化的对象作为方法参数传递,那能不能将方法当作参数传递呢?C#提供的委托就可以实现这个功能。委托类似于C语言中的函数指针,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递。 使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时,可以依次调用所有绑定的方法。使用委托需要执行以下步骤。 (1)定义委托 (2)实例化委托 (3)调用委托 定义委托跟定义方法类似,只是没有方法体,语法如下: public delegat
26、e void Login(string name,string password);,7.8.2 实例化委托,【本节示例参考:源代码chapter77.8.2DelegateExample】 委托定义好以后,下面就可以实例化委托了,实例化委托意味着引用某个方法。要实例化委托,就要调用该委托的构造函数,并将与该委托关联的方法作为其参数传递。,7.8.3 调用委托,【本节示例参考:源代码chapter77.8.3DelegateExample】 调用委托即是用委托对方法进行实例化,调用委托与调用方法类似,惟一的区别在于不是调用委托的实现,而是调用委托关联的方法的实现。,7.8.4 事件的定义,C#
27、使用delegate和event关键字提供了一个简洁的事件处理方案。C#中的事件允许一个对象将发生的事件通知其他对象。将发生的事件通知给其他对象的对象称为发行者,对象可以订阅事件,该对象称为订阅者。一个事件可以有一个或者多个订阅者,事件的发行者也可以是该事件的订阅者。 举个例子,田径赛场上的运动员都准备好了,观众都聚精会神地等着裁判员的哨声。裁判员预备,吹哨,所有运动员如万箭齐发。这里的裁判员就是事件源,称为发行者。运动员都会对这个事件产生响应,是事件订阅者,而观众就没有订阅该事件,也不会参加赛跑。C#中的事件处理主要有以下几个步骤。 (1)定义事件 (2)订阅事件 (3)触发事件,7.8.5
28、 订阅事件,订阅事件只是添加了一个委托,当引发事件时,该委托将调用一个方法。订阅事件的语法如下: eventTest += new delegateTest(obj1.Method); /对象obj1订阅事件 eventTest += new delegateTest(obj2.Method); /对象obj2订阅事件 代码中,obj1和obj2两个对象分别订阅了事件eventTest。当事件eventTest被引发时,则会执行对象obj1的名为Method的方法,以及对象obj2的名为Method的方法。 技巧:在Visual Studio 2008开发环境中,可以通过快捷方式来订阅事件:输
29、入“事件名+=”,这时候程序会弹出提示,按一下“Tab”键,创建委托;这时候,光标会移动到委托调用的参数上,再按一下“Tab”键,程序将自动生成委托调用的函数。,7.8.6 引发事件,【本节示例参考:源代码chapter77.8.6EventExample】 引发事件和调用方法类似,如下面的代码所示。 if(condition) /引发事件 eventTest(); 引发事件eventTest时,将调用订阅此特定事件的对象的所有委托,如果没有对象订阅该事件,则事件引发时,会发生异常。下面通过一个完整的例子来总结委托和事件两部分知识。,7.9 小结,通过本章,我们学习了以下内容。 (1)了解面向
30、对象封装、继承、多态三大特性。封装:保证对象自身数据的完整性、安全性。继承:建立类之间的关系,实现代码重用,方便系统扩展。多态:相同的方法调用可实现不同的实现方式。 (2)接口可以约束类的行为,使不同的类能达到一个统一的规范。接口是对类的更深一层抽象。 (3)里氏替换原则:原则上子类对象是可以赋给父类对象的,即子类可以完全替代父类并且出现在父类能够出现的地方。但是反过来,父类对象是不能替换子类对象的。 (4)把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。 (5)程序集是.NET框架应用程序的生成块,它包含编译好的代码逻辑单元。反射可以获取已加载的程序
31、集和在其中定义的类型信息。 (6)索引器允许类或结构的实例按照与数组相同的方式进行索引。索引类似于属性,不同之处在于索引器采用参数。 (7)委托定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递。事件允许一个对象将发生的事件通知其他对象。,本章习题,一、选择题 1在面向对象编程中,子类继承父类,下面说法错误的是( ) A子类继承父类,也可以说父类派生自一个子类 B子类可以继承父类的所有成员 C一个子类不能继承多个父类 D父类还可以成为其他类的子类 2下面关于base关键字的说法,错误的是( ) A在子类中,base关键字可以访问父类的属性 B在子类中,base关键字不可以调用父类的构造方法 C在子类中,base关键字不可以调用父类的私有方法 D在子类中,base关键字不可以访问父类的私有字段 3下C# 中,下列代码中可以用于类型转换的是( ) APerson is Student BPerson as Student CStudent is Person D(Student)Person 二、简答题 1简述面向对象的三大特性,并简述每个特性的基本功能。 2简述抽象方法与虚方法的区别。,
链接地址:https://www.31doc.com/p-2664367.html