第五章 面向对象编程    
 
  Visual Basic .NET语言是一种面向对象的程序设计语言。软件开发的过程是对问题的求解过程,从认识角度出发,这一过程涉及到人们对所要解决问题及其相关事物的认识以及基于这种认识对事物的描述。对事物的认识是指,人们通过对事物及其与周围世界的联系进行观察、分析、思考,抽象出反应事物本质的特性和属性,了解事物的行为和功能,并提出解决问题的方法。对事物的描述是将对事物的认识及解决方案,用自然语言、数字语言、最终用计算机语言表达出来。
  主要内容:
  面向对象程序设计
  对象
  
  构造函数
  类的继承

  5.1 面向对象程序设计的概念

  早期的程序设计经历了"面向问题"、"面向过程"的阶段,随着计算机技术的发展,以及所要解决问题的复杂性越来越高,以往的程序设计方法已经不能适应发展的需要。面向对象的程序设计是20世纪80年代初提出来的,起源于SmallTalk语言。这种方法引入了新的概念和思维方式,为使软件容易在程序设计中能够模仿建立真实世界模型的方法,对系统的复杂性进行概括、抽象和分类,使软件的设计与实现形成一个由抽象到具体、由简单到复杂这样一个循序渐进的过程,从而解决了大型软件研制中存在的效率低、质量难以保证、调试复杂、维护困难等一系列问题。面向对象的程序设计涉及到对象、封装、类、继承及多态等几个基本概念。

  5.1.1 对象
  对象就是存在的一切事物。它可以是一个人、一辆汽车、一只鸟、一个报表等等。对象是构成现实世界的一个独立单位,人们对世界的认识,是从分析对象的特征入手的。
  对象的特征分为静态和动态两种。静态的特征指对象的外观、性质、属性等;动态的特征指对象具有的功能、行为等。客观事物是错综复杂的,但人们总是从某一目的出发,运用抽象的能力,从众多的特征中抽取最有代表性、最能反映对象本质的若干特征加以详细研究。
  人们将对象的静态特征抽象为属性,用数据来描述,在Visual Basic .NET中表示为变量;人们将对象的动态特征抽象为行为,在Visual Basic .NET中用一组代码来表示,称为方法来完成对数据的操作。一个对象由一组属性和一组对属性进行操作的服务构成。
  下面来看一个例子。这里用鸟作比喻,来理解面向对象这个概念。我们会经常看到鸟,鸟作为一个研究对象它具有以下静态特征:
  有颜色
  有羽毛
  有翅膀
  有大小
  有爪子等
  动态特征:
  飞行
  发出不同的声音
  走动等
  我们可以将具有以上几种特征的动物抽象为一类动物,我们称为鸟类。我们可以想象大自然这个设计者,可以任意改变鸟类的这些基本特征,完成它的作品形成不同的鸟,如燕子、麻雀、鹰等等。至于如何创作我们不可能知道,只有大自然这个设计者明白。

  5.1.2 封装
  面向对象的核心概念是封装。它有两个含义:一是指把对象的属性和行为看成为一个密不可分的整体,将这两者"封装"在一个不可分割的独立单位(即对象)中。另一层含义指"信息隐蔽",把不需要让外界知道的信息隐蔽起来,有些对象的属性及行为允许外界用户知道或使用,但不允许更改,而另一些属性或行为,则不允许外界知晓;或只允许使用对象功能,而尽可能隐蔽对象的功能细节。
  封装机制在程序设计中表现为,把描述对象属性的变量及实现对象功能的方法合在一起,定义为一个程序单位,并保证外界不能任意更改其内部的属性值,也不能任意调动其内部的功能方法。
  封装机制的另一个特点是,为封装在一个整体内的变量及方法规定了不同级别的"可见性"或访问权限。

  5.1.3 类
  我们将具有相同属性及相同行为的一批对象称为类。广义地讲,把具有共同性质的事物的集合称为类。
  在面向对象程序设计中,类是一个独立的程序单位,它有一个类名,其内部包括成员变量,用于描述对象的属性;还包括类的成员方法,用于描述对象的行为。在Visual Basic .NET中,类被认为是一种抽象数据类型,这种数据类型,不但包括数据,还包括方法。这扩大了数据类型的概念。
  我们刚才举了一个鸟对象的例子,说明类是一个抽象的概念。要利用类的方式解决问题,必须用类创建一个实例化的类对象,然后通过类对象去访问类的成员变量,去掉用类的成员方法。比如我们大自然要创建一种鸟叫乌鸦:
  鸟类 乌鸦=new 鸟类("黑色","会飞"等等)
  因为类是一种扩充的数据类型,我们可以用定义整型变量方法,乌鸦是鸟类的对象化。

  5.1.4 类的继承
  继承是面向对象方法中的重要概念,并且是提高软件开发效率的重要手段。
  我们首先拥有反映事物一般特性的类,然后在其基础上派生出反映特殊事物的类。我们还是以鸟类作为例子,我们知道家鸡是野生鸡的演变,家鸡作为一类它继承了野生鸡的基本特征,又加入了新的内容。
  在Visual Basic .NET程序设计中,已有的类可以是Visual Basic .NET提供的最基本一批程序---类库。用户开发的程序类是继承已有的类,这样,现在类所描述过的属性及行为,即已定义的变量和方法,在继承产生的类中完全可以使用。被继承的类称为父类或超类,经继承产生的类称为子类或派生类。根据继承机制。派生类完全继承超类的所有成员,并相应增加自己的新成员。
  面向对象程序设计中的继承机制,大大提高了程序代码的可重复利用率,提高了软件开发效率,减少程序产生错误的机会,也为程序的修改扩充提供了便利。
  一个子类只允许继承成一个父类,称为单继承;允许从多个父类中继承,称为多继承。目前许多面向对象程序设计语言不支持多继承。Visual Basic .NET通过接口(interface)的方式,类弥补由于Visual Basic .NET不支持多继承而带来的子类不能享用多个父类成元的缺憾。

  5.1.5 类的多态性
  多态是面向对象程序设计的有一个重要特性。多态是允许程序中出现重名相象。Visual Basic .NET语言中具有方法冲在与成员覆盖两种形式的多态。
  方法重载:在一个类中,允许多个方法使用一个名字,但它们的方法参数不同,完成的功能不同。
  成员的覆盖:子类与父类允许具有相同的变量名称,但数据类型不同,允许具有相同的方法名称,但完成的功能不同。
  多态的特性使程序的抽象程度和简洁程度更高,有助于程序的分组协同开发。

  5.2 第一个对象

  类是Visual Basic .NET中的一个复合数据类型,是基本的编译单位。Visual Basic .NET的类具有两种基本成分,数据和方法。类的这两种成分抽象地概括了一组具有相同特点的实现对象的属性和行为,并将这两者封装在类体中,与外界隔开。
  在下面这个理论性相当强的例子中,将介绍对象的一些特性,比如汽车。
  对于对象,我们想知道属性的包括:
  外观----包括结构、型号、颜色和门的数量等
  性能----马力、引擎大小、汽缸配置等
  功能----当前位置采用GPS定位。
  我们还想控制对象行为方法,例如:
  加速
  减速
  向左转
  向右转
  向前开
  三点转向
  完全停止

  5.2.1 定义类
  类是用户定义的数据类型,可以把它作为模块的一部分,通常在单独的文件中进行声明,这些文件使用相同的扩展名.vb。
  例 5.1 创建Car类
  (1) 运行Visual Basic .NET,从菜单中选择文件 | 新建 | 工程 命令,创建一个新工程。
  (2) 当出现新建项目对话框时,选择Visual Basic .NET 控制台应用程序模板,并输入新工程名称T5-1,如图5-1所示。单击确定按钮,创建工程。
            
                     图5-1
  (3) 现在需要创建一个新类。这就要使用解决方案资源管理器窗体,右击Objects工程并选择添加 |添加类 。打开的对话框提示输入新类的名称,出入Car并单击打开按钮,如图5-2所示。
           
                    图5-2
  (4) 注意在解决方案资源管理器窗体中添加了新类和新模块。编辑器现在显示的是该类和模块的代码,如图5-3所示。
               
            图5-3               图5-4
  (5) 这就是创建类所需完成的工作。在代码编辑器的顶端如图5-4所示。
  每个类和模块都存储在他们自己的文件中,而这个选项卡提供了一种有用的方法,可以在打开的文件间切换。另外,也可以使用Ctrl+Tab组合键或双击解决方案资源管理器中的文件来打开它。

  5.2.2 存储属性
  属性是用来描述对象本身的,如果赋予汽车对象一些属性,例如汽车是蓝颜色的,这就为汽车提供了如下状态:"所表示的车是蓝颜色"。
  如何在类中管理属性?属性通常存储在变量中,而变量是在类中定义的。通常,以构建的属性和方法将在某些方面影响或使用状态。假定构建了改变汽车颜色的属性。当设置属性时,用来存储属性的变量就会改变,以反映所指定的新值。当获取该属性时,就应读取存储属性的变量,并把当前值返回给调用者。
  于是,从这方面看,属性就是行为,一个公共属性实际上是两个方法:Get方法和Set方法。Color属性的Get方法包含的代码可以告诉调用者,汽车的颜色是什么。Color属性的Set方法则设置一个表示汽车颜色的值。但在真正的应用程序中,Color并不仅仅存储一个值。例如,在赛车游戏中,Color属性的Set方法可以修改屏幕上汽车的颜色。
  例 5.2 添加属性
  (1) 继续例5.1的工程,打开Car.vb文件,并添加如下代码:
  Public Class Car
  Public Color As String
  End Class
  (2) 完成后,下面需要使用类看看该属性如何工作。打开Module.vb文件,并添加如下代码:
  Module Module1
  Sub Main()
  Dim myCar As Car
  myCar = New Car()
  myCar.Color = "Red"
  Console.WriteLine("My car is this color:")
  Console.WriteLine(myCar.Color)
  Console.ReadLine()
  End Sub
  End Module
  (3) 运行该工程,显示一个新窗口,如图5-5所示。
             
                    图5-5
  〖代码的说明〗
  定义属性非常简单,下面代码行:
  Public Color As String
  告诉类我们想创建一个变量Color,而且该属性存储一个文本字符串。在声明变量Color时使用Public关键字,是说明使用Car类的用户可以访问该变量,而不仅仅能从类的内部访问它。变量在Public Class和End Class之间定义,在函数定义的外部时,变量就称为成员变量。
  我们将在Module1.vb中使用对象。首先实例化类的实例,在下面的代码行中,创建一个变量myCar,并要求它存储使用Car类创建对象:
  Sub Main()
  Dim myCar As Car
  定义了变量后,该变量还没有与之相关联的对象实例,而只是指定了对象的类型。这有点类似于告诉计算机提供一个空间,以便关联Car对象,并把该空间称为myCar。目前还可以把任何内容与它关联起来。为此,必须创建一个对象的实例。这可以使用New关键字来实现,如下所示:
  myCar=New Car()
  这行代码的意思是:"让myCar表示从Car类中实例化的新对象"。换言之,创建一个新汽车对象,把它myCar关联起来。现在有了一个Car对象,就可以用myCar这个名字来表示它。
  创建完一个对象实例后,就可以设置其属性,调用其方法。下面介绍如何设置
  Color属性:
  myCar.Color="Red"
  设置完属性后,就可以对它检索任意次,或在以后改变它的值。下面将WriteLine方法传递给Console对象,解释一下检索:
  Console.WriteLine("My car is this color:")
  Console.WriteLine(myCar.Color)
  Console.ReadLine()
  Console.ReadLine()代码行的作用是在按下Enter键后结束程序。控制台应用程序比较适合于测试内存中的对象,因为不需要建立用户界面。我们还可以显示希望显示的文本行。在Windows应用程序中,这些对象也会按照我们希望的方式工作。
  这并不是一个真正的属性,在使用类的开发人员看来,它只是像一个属性那样工作。实际上,"真正"的属性是对类的用户来说看起来像是变量的方法,是使用方法还是使用属性,取决于类的用户觉得使用哪一个更快。
  使用真正的属性,其最简单的原因是防止类的用户直接改变其值。这种属性称为只读属性。汽车的速度就是一个只读属性。如果速度是60mph,就不能把速度改为我们想要的值,而应使用速度的方法(Accelelerate、Decelerate),用一个只读属性Speed报告汽车的当前速度。
  这个例子很好地说明了模拟真实对象的类应如何像一个真正的对象那样工作。我们可以从速度计中读取汽车的速度,但不能用手指转动速度计的指针,来修改汽车的速度,而必须用另外一种方式控制汽车的速度,即使用油门来加速或减速。
  这里需要一个只能由类本身查看和操纵的成员变量,这可以使用Private关键字来实现:
  Public Color As String
  Private _Speed As Integer
  注意_Speed被标记为Private,因此只能由在类中定义的函数访问。Car的用户甚至不知道该属性的存在。
  例 5.3 添加真正的属性Speed
  1.使用Private代替Public关键字定一个私有变量,给Car.vb文件添加如下语句:
  Public Class Car
  Public Color As String
  Private _speed As Integer
  End Class
  2.为了显示速度,需要构建一个只读属性。添加如下代码:
  Public Class Car
  Public Color As String
  Private _speed As Integer
  ReadOnly Property Speed() As Integer
  Get
  Return _speed
  End Get
  End Property
  End Class
  3.现在构建一个方法Accelerate,它通过指定每小时多少英里来调节汽车的速度:
  Public Class Car
  Public Color As String
  Private _speed As Integer
  ReadOnly Property Speed() As Integer
  Get
  Return _speed
  End Get
  End Property
  Sub Accelerate(ByVal accelerateBy As Integer)
  _speed += accelerateBy
  End Sub
  End Class
  4.为了测试对象,需对Module1.vb作些修改。打开文件,并添加如下代码,替代以前的代码:
  Sub Main()
  Dim myCar As Car
  myCar = New Car()

  Console.WriteLine("这辆汽车的速度是:")
  Console.WriteLine(myCar.Speed)
  myCar.Accelerate(5)
  Console.WriteLine("这辆汽车的现在速度是:")
  Console.WriteLine(myCar.Speed)
  Console.ReadLine()
  End Sub
  5.运行该工程,得到如图5-6所示的结果。
              
                    图5-6
  〖代码的说明〗
  首先定义一个私有成员变量_speed。
  Private _speed As Integer
  在默认情况下,当创建对象时,_speed的值为0,因为这是数据类型Integer的默认值。接着定义一个返回当前速度的只读属性:
  ReadOnly Property Speed() As Integer
  Get
  Return _speed
  End Get
  End Property
  在定义属性时,可以把它们设置为只读ReadOnly关键字,只写WriteOnly(还未介绍)或可读写(不使用上述任何一个关键字)。读入属性就是获取值,而写入属性就是设置值。当读入属性时,就执行Get和End Get之间的代码。只需返回当前存储在_speed中的值。
  本例还创建了一个方法Accelerate。这个方法没有返回值,所以使用Sub关键字:
  Sub Accelerate(ByVal accelerateBy As Integer)
  _speed += accelerateBy
  End Sub
  这个方法带一个参数即accelerateBy,用于告诉方法把速度加快多少。注意该方法的唯一动作就是调整内部成员_speed。
  汽车加速也用到了封装的概念。在现实世界中,汽车的加速需要一些加大油门的传动装置来达到所需的速度,而对象的用户不知道这一切是如何工作的,只需知道告诉汽车加速即可。使用这些功能十分简单。首先创建一个对象:
  Dim myCar As Car
  myCar = New Car()
  在使用Color属性的代码后面,输出速度:
  Console.WriteLine("这辆汽车的速度是:")
  Console.WriteLine(myCar.Speed)
  注意如何只用只读的Speed属性来获得当前的汽车速度。最初创建对象时,内部的_speed成员被设置为0:现在调用Accelerate方法,使用它加速汽车的速度:
  myCar.Accelerate(5)
  最后,输出新的速度:
  Console.WriteLine("这辆汽车的现在速度是:")
  Console.WriteLine(myCar.Speed)
  现在来处理门的数量问题。
  例题 5.4 添加NumberOfDoors属性
  1.首先需要构造一个存储门的数量的私有成员,这里把这个属性的默认值定义为5。添加如下代码:
  Public Color As String
  Private _speed As Integer
  Private _numberOfDoors As Integer = 5
  2.现在就可以构建一个属性来获得和设置门的数量,假定要设置门数总是在2-5之间。在Car.vb中,把下面的代码添加到Accelerate方法的下面:
  Property NumberOfDoors() As Integer
  Get
  Return _numberOfDoors
  End Get
  Set(ByVal Value As Integer)
  If Value >= 2 And Value <= 5 Then
  _numberOfDoors = Value
  End If
  End Set
  End Property
  3.为了测试属性,需要再次修改Module1.vb:
  Sub Main()
  Dim myCar As Car
  myCar = New Car()

  Console.WriteLine("这辆汽车的门数是:")
  Console.WriteLine(myCar.NumberOfDoors)
  myCar.NumberOfDoors = 1000
  Console.WriteLine("这辆汽车的门数是:")
  Console.WriteLine(myCar.NumberOfDoors)
  myCar.NumberOfDoors = 1000
  Console.WriteLine("这辆汽车的门数是:")
  Console.WriteLine(myCar.NumberOfDoors)
  Console.ReadLine()
  End Sub
  4.运行该工程,其结果如图5-7所示。
               
                     图5-7
  〖代码的说明〗
  首先定义一个_numberOfDoors私用成员变量,并将5赋给这个变量。
  Private _numberOfDoors As Integer = 5
  此时设置值的目的很简单:我们希望_numberOfDoors的值总是在2到5之间。创建时,_numberOfDoors将被赋值为5。如果没有赋值,则_numberOfDoors将会默认为0。这会与门的数量必须在2到5这个范围之内互相矛盾,应防止出现这种情况。
  接下来就是属性部分。Get部分很简单-只是返回存储在_numberOfDoors中的值。Set部分测试新值是否有效。新值是通过参数Value来传递的:
  Property NumberOfDoors() As Integer
  Get
  Return _numberOfDoors
  End Get
  Set(ByVal Value As Integer)
  If Value >= 2 And Value <= 5 Then
  _numberOfDoors = Value
  End If
  End Set
  End Property
  添加到Module1.vb中的测试代码不是非常复杂,这些代码只是显示_numberOfDoors的初始值,然后把其值改为1000。如果使用了不一致的值,_numberOfDoors属性中的验证代码将不改变_numberOfDoors,所以,当再次显示门的数量时,所的结果仍然是5。最后,把其值设置为2。这个有效值,再次显示门的数量,结果为2。

  5.2.3 IsMoving方法
  在构建对象时,要经常考虑一个问题:"如果使对象更容易使用?"例如,客户需要知道汽车是否在移动,那么该怎样才能简单地确定这一点?
  一种方式是查看Speed属性,如果其值为零,则车是停止的,但是使用对象的开发人员要理解这一点,需要依赖他对所模拟的对象的了解。最好创建方法来处理这类事件。下面创建IsMoving方法来解决这个问题。
  例 5.5 添加IsMoving方法
  1.IsMoving方法只需要查看汽车的速度,并根据汽车是否在移动,返回True或False。在Car.vb中添加如下代码:
  Public Function IsMoving() As Boolean
  If Speed = 0 Then
  Return False
  Else
  Return True
  End If
  End Function
  2.为了测试属性,对Module1.vb作如下改变:
  Sub Main()
  Dim myCar As Car
  myCar = New Car()

  Console.WriteLine("这辆汽车的门数是:")
  Console.WriteLine(myCar.NumberOfDoors)
  myCar.Accelerate(25)
  If myCar.IsMoving = True Then
  Console.WriteLine("这辆汽车正在行驶")
  Else
  Console.WriteLine("这辆汽车正在停止")
  End If
  Console.ReadLine()
  End Sub
  3.运行此工程,其结果如图5-8所示。
               
                   图5-8
  〖代码的说明〗
  这个例子创建了一个简单的方法来测试Speed属性的值,如果速度不为零则返回Ture,否则返回False。
  Public Function IsMoving() As Boolean
  If Speed = 0 Then
  Return False
  Else
  Return True
  End If
  End Function
  这个方法很简单,用户很容易知道对象是否在移动。根据一个属性的值来判断汽车是否在移动,是不会产生混淆的;这个简单的方法将返回一个确定的答案。
  这里为什么要使用一个方法,而该方法实际上是一个属性?该方法仅报告对象的状态,并不影响对象的行为。本例不使用属性并没有什么特别的理由。但使用方法可以告诉对象的用户,这个值是计算出来的,而不仅仅报告一个内部变量的值。它还给例子添加了一些变数,说明添加方法是非常简单的。

  5.3 构造函数

  构造函数是用来对类对象的成员进行初始化。当创建类的某个对象时,该类的构造函数自动调用,同时,在类名右边的括号内可以提供初始值,把它们作为参数传送到构造函数中。
  如果类中没有定义构造函数,编译器会自动创建一个缺省的不带参数的构造函数。类的缺省构造函数调用直接父类的不带参数的构造函数,并初始化实例变量。一个类中可以含有几个构造函数,这是通过构造函数的重载来实现的。
  例题 5.6 构造函数
  1.为了便于讨论构造函数,首先从_numberOfDoors成员中删除默认值5,并对car.vb作如下改变:
  Public Class Car
  Public Color As String
  Private _speed As Integer
  Private _numberOfDoors As Integer
  2.现在,添加下列方法以构建函数,在创建Car对象时,就会执行这个方法中的代码:
  Public Class Car
  Public Color As String
  Private _speed As Integer
  Private _numberOfDoors As Integer
  Sub New()
  Color = "White"
  _speed = 0
  _numberOfDoors = 5
  End Sub
  3.为了测试构造函数的作用,创建一个函数,显示汽车的详细资料。对Module1.vb作如下修改:
  Module Module1
  Sub Main()
  Dim myCar As Car
  myCar=New Car()
  DisplayCarDetails(myCar)
  End Sub
  Function DisplayCarDetails(ByVal myCar As Car)
  Console.WriteLine("颜色:" & myCar.Color)
  Console.WriteLine("门数:" & myCar.numberOfDoors)
  Console.WriteLine("当前速度:" & myCar.Speed)
  Console.WriteLine()
  End Function
  End Module
  4.现在运行该工程,其结果如图5-9所示。
               
                     图5-9
  〖代码的说明〗
  只要创建对象,就会调用构造函数中的代码,在其中可以为成员函数赋值:
  Sub New()
  Color = "White"
  _speed = 0
  _numberOfDoors = 5
  End Sub

  5.4 深入面向对象的程序设计

  本节讨论面向对象的程序设计(object-oriented programming,OPP)及其关键技术:继承性和多态性。
  继承性是软件的一种重用形式。新类从已存在的类中产生,通过保留它们的属性(变量)和行为(方法),并根据新类的性能要求加以修改,添加新的属性和行为。软件重用可以缩短软件开发时间,重用那些已被证实和经过调试的高质量软件,可以提高系统性能,减少系统在使用过程中出现问题。
  多态性允许以统一的风格处理已存在的变量及相关的类。多态性使向系统中添加新功能变得容易。
  继承性和多态性是降低软件复杂性的有效技术。
  当用户建立一个新类时,不必全部写出所有的实例变量和实例方法,只需声明该类继承以定义过的"父类"的实例变量和实例方法即可。这个新类称作"子类",每个子类也可以成为将来某个子类的父类。
  子类的直接父类是该子类直接继承的类,而间接父类则是两级或两级以上的父类。
  子类从一个父类中派生出来,称单一继承。如果一个子类有多个直接父类:则称为多重继承。Visual Basic .NET不支持多重继承,但它支持接口概念。接口使Visual Baisc .NET获得了多重继承的许多优点,并舍弃了由于多重继承带来的某些缺点。
  用户通常要在子类中加入自己的实例变量和实例方法,所以子类比它的父类大,但更具特殊性,代表一组更小的对象。在单一继承中,子类和它的父类的出发点基本上是相同的,继承的真正力量来自于子类定义时添加的功能,或者对从其父类继承来的某些功能的修改。
  继承性的一个问题是,子类也可以继承它不需要的属性和方法。当一个父类成员不适合该子类时,子类会以恰当的方式重新定义它。子类对从父类继承来的属性重新定义,称为属性的隐藏;子类对从父类继承来的方法重新定义,称为方法的覆盖。子类覆盖父类的方法时,方法头应该与父类完全一样,只是方法体不一样,从而完成满足子类要求的、与父类的同名方法不同的功能。

  5.4.1继承性
  实际上,继承允许添加新方法和属性,或替换已有的方法和属性,以利用另一个类支持自己的功能。我们应从通用的汽车类中创建出更具体的车,例如跑车、货车和拖车等。
  所以,如果想模拟一辆跑车,应使默认的门数2而不是5,还可以用属性和方法来帮助理解汽车的性能。继承是控制对公共成员和私有成员的访问的一种方式。任何公共成员如Color,都可以由其派生类访问。但私有成员像_speed不是这样。也就是说,如果SportsCar类要改变Car的速度,就必须通过Car类的属性和方法来实现。
  下面通过创建一个新类SportsCar来说明继承这个概念,SportsCar类继承于Car类,并能查看跑车的功率重量比。
  例 5.7 从Car类中继承
  1.为了解释继承这个概念,需要为Car类添加一个公共变量,表示汽车的马力。当然,如果要使本例更强健,应使用一个属性,并确保该属性使用某范围内的值。但这里为了使程序比较简单,运行起来比较快,打开Car.vb文件,并添加下列代码:
  Public Class Car
  Public Color As String
  Public HorsePower As Integer
  Private _speed As Integer
  Private _numberOfDoors As Integer
  2.像以前一样创建一个新类,方法是右击解决方案资源管理其中的Car工程,选择添加 | 添加类 选项,然后输入类名SportsCar,单击打开按钮,如图5-10所示。
            
                     图5-10
  3.为了说明SportsCar类是继承于Car类的,需要使用Inherits关键字。在SportCar.vb中添加下列代码:
  Public Class SportsCar
  Inherits Car
  End Class
  4.这样,SportCar类就继承了Car类的所有方法和属性。接下来添加一个新的公共变量Weight:
  Public Class SportsCar
  Inherits Car
  Public Weight As Integer
  End Class
  5.为了测试这个新属性,需要对Module1.vb作些改变。特别注意,为了获得Weight属性,需要创建一个SportsCar对象,而不是Car对象:
  Sub Main()
  Dim mySportsCar As SportsCar
  mySportsCar=New SportsCar()
  MySportsCar.HorsePower = 240
  MySportsCar.Weight = 1085
  Console.WriteLine("马力:" & mySportsCar.HorsePower)
  Console.WriteLine("重量:" & mySportsCar.Weight)
  End Sub
  6.运行该工程。得到如图5-11所示的结果。
             
                   图5-11
  〖代码的说明〗
  使SportsCar类继承于Car类的命令是用Inherits关键字实现的:
  Public Class SportsCar
  Inherits Car
  这时,新类SportsCar就包括了Car类中的所有方法和属性,但它不能查看和修改私有成员变量。
构造函数是创建对象时调用的方法,它可以使对象进入开发人员能使用它的状态。在这个构造函数中,我们把_numberOfDoors的默认值设置为5,但对于跑车而言,这个值应该是2。
  被继承的类叫作基类。如果用自己的方法或属性替换已有的方法或属性,这个过程称为重写。
  例 5.8 重写构造函数
  1.重写构造函数时,只需在新类SportsCar中创建自己的构造函数。给SportCar添加如下代码:
  Public Class SportsCar
  Inherits Car
  Public Weight As Integer
  Sub New()
  Color = "Green"
  NumberOfDoors=2
  End Sub
  End Class
  2.为了测试这个构造函数,需要显示SportsCar的内容。下面添加对DisplayCarDetails的调用,并在MySportsCar中传递。把下面这些内容添加到Module1.vb中:
  Sub Main()
  Dim mySportsCar As SportsCar
  mySportsCar=New SportsCar()
  DisplayCarDetails(mySportsCar)
  MySportsCar.HorsePower = 240
  MySportsCar.Weight = 1085
  Function DisplayCarDetails(ByVal myCar As Car)
  Console.WriteLine("颜色:" & myCar.Color)
  Console.WriteLine("门数:" & myCar.numberOfDoors)
  Console.WriteLine("当前速度:" & myCar.Speed)
  Console.WriteLine()
  End Function
  Console.WriteLine("马力:" & mySportsCar.HorsePower)
  Console.WriteLine("重量:" & mySportsCar.Weight)
  End Sub
  〖代码的说明〗
  在SportsCar中添加的新构造函数重写了Car类中的已有的构造函数。.NET会在运行新的构造函数之前运行基类的构造函数中的代码,所以,事实上是先运行如下代码:
  Sub New()
  Color = "White"
  _speed = 0
  _numberOfDoors = 5
  End Sub
  接着运行下列代码:
  Sub New()
  Color = "Green"
  NumberOfDoors=2
  End Sub

  5.4.2 多态性
  利用多态性可以方便地设计和实现具有良好扩展性的系统。回想一下DisplayCarDtails的代码:
  Function DisplayCarDetails(ByVal myCar As Car)
  Console.WriteLine("颜色:" & myCar.Color)
  Console.WriteLine("门数:" & myCar.numberOfDoors)
  Console.WriteLine("当前速度:" & myCar.Speed)
  Console.WriteLine()
  End Function
  第一行说明了我们想接受的参数是一个Car对象。但是,当调用这个对象时,实际上给它传递了一个SportCar对象。下面看看如何创建该对象并调用DisplayCarDetails:
  Dim mySportsCar As SportsCar
  mySportsCar=New SportsCar()
  DisplayCarDetails(mySportsCar)
  如果函数带有一个Car对象参数,那么如何给它传递一个SportCar对象呢?我么可以把SportCar对象当作Car对象,因为SportsCar继承于Car。继承规定了SportCar对象所有的功能都包括在Car对象的功能中,因此可以用同样的方式对待这两个对象。如果需要对Car对象调用一个方法,SportsCar对象也必须能实现这个方法。
  反过来就不是这样,如果定义了这样一个函数:
  Function DisplaySportsCar(ByVal mySportsCar as SportCar)
  不能给这个函数传递一个Car对象。不能保证Car对象能实现SportsCar对象的所有功能,因为给SportsCar对象添加的额外方法和属性Car对象所不具备的。SportsCar对象是一种更特殊的Car对象。
  总之,多态性是指使对象看起来像另一个对象,开发人员不必兜圈子来使之实现的一种规则。

  5.5 Framework类

  .NET Framework是一个巨大的类集合,它包含了大约3500个类。Framework分为许多命名空间,命名空间把相似的类组合在一起。如果要查找某种特定的功能,使用命名空间可以缩小搜索的范围。这些命名空间实际上也是分层的,一个命名空间可以包含其他命名空间,后者把更为类似的类组合在一起。每个类一定属于一个命名空间。
  大多数的Framework类都集中在System命名空间中,或者说System包括所有命名空间。例如:
  System.Data中的类可以访问存储在数据库中的数据。
  System.Xml中的类可以读写XML文档。
  System.Windows.Forms中的类可以在屏幕上绘制窗口。
  System.Net中的类用于网络通信。
  命名空间的存在意味着所有对象的实际名称比在软件代码中的名称长。到现在为止,我们一直使用简写符号来表示类。
  事实上,所有对象不是都派生于Object,因为Object包含在System命名空间里,它的全称是System.Object。同样,Console是System..Console的缩写,这意味着下面这一行代码:
  Console.ReadLine() 等同于 System.Console.ReadLine()
  .NET会自动创建System中所有类的简写形式,所以不必输入System。
  每个类都属于某个命名空间,但是自己创建的类应属于哪个命名空间?工程中有一个默认的命名空间,新类都放到这个命名空间里。
  例题 5.9 命名空间的名称
  1.为了查看当前所使用的命名空间,右击解决方案资源管理其中的Car工程并选择属性选项。
  2.Objects属性页窗口中的根命名空间项目提供了用来放置新类的命名空间,如图5-12所示。
          
                     图5-12
  3.类的前缀为Objects.,如下所示:
  Car类实际上是Objects.Car。
  SportsCar类实际上是Objects.SportsCar。
  使用命名空间的目的是便于开发人员使用类。假设工程被另一个开发人员使用,而他已经创建了自己的Car类,此时如何区分这两个类?实际上我们的类叫做Objects.Car,而他们的类是MyCar.Car或是YYY.Car命名空间可以清晰地分别不同的类。当然,这个命名空间并不好,因为它并没有描述出其中包含那些类,这里的命名空间仅用于说明本章的目的。
  1.Imports语句
  不需要为类添加前缀Car或System,因为.NET会自动创建类的简写形式,通过使用Import语句。
  在上一章,窗体顶部有下列代码:
  Imports System.IO
  Public ClassForm1
  Inherits System.Windows.Forms.Form
  该窗体的按钮下面有如下代码:
  Private Sub Button1_Click(ByVal sender As System.Object,
  ByVal e As System.EventArgs) Handles Button1.Click
  Dim subfolders() As DirectoryInfo
  subfolders = New DirectoryInfo("c:\").GetDirectories
  Dim subfolder As DirectoryInfo
  For Each subfolder In subfolders
  ListBox1.Items.Add(subfolder.FullName)
  Next
  End Sub
  这里使用Imports System.IO语句将System.IO命名空间导入工程中。这样做是因为我们想使用DirectoryInfo类。这个类的全称是System.IO.DirectoryInfo,但因为添加了一个命名空间导入声明,所以就可以用DirectotyInfo来代替。
  所有的Imports语句一定要写在所有使用的代码文件的顶部,在其他任何代码之前。
  2.创建自己的命名空间
  将Namespace…End Nanespace定义封装到Class…End Class定中,就可以定义命名空间。默认情况下,Visual Basic .NET中创建的类会自动指定到根命名空间中。Visual Basic .NET会自动根据工程名称为这个根命名空间命名。
  例 5.10 创建命名空间
  1.使用解决方案资源管理器,右击工程并选择属性选项。通用属性| 常规 窗格选项中的根命名空间文本框给出了它的名称。在这个例子中,根命名空间为Objects,如图5-13所示,浏览完命名空间后,单击取消按钮。
         
                     图5-13
  2.Visual Studio .NET的对象浏览器是个非常有用的工具,它给出了工程中的可用的类。从菜单中依次选择视图|其他窗体|对象浏览器,就可以打开对象浏览器选项卡。显示对象浏览器后,第一个项目通常是一个工程。这样就可以向下浏览,找到Car类,如图5-14所示。
          
                    图5-14
  3.注意在这个对话框中还可以看到这个类的方法、属性和成员变量。因为这里要讨论的是命名空间。它就在类的上面,用花括号图标表示({})。因为命名空间的名称为Objects,所以,类的全名是Objects.Car。
  3.Framework中的继承
  在.NET中,对继承需要了解的是,类不能继承于多个类。因为所有的类都继承于System.Object,结果必然是所有的类都只继承于一个类。
  每个类都只能继承于一个类,其含义是每个类只能在其Inherits子句中包含一个类。被继承的类可以继承于另一个类。例如,可以创建一个类,它继承于SporrtsCar类,于是可以说,新类间接继承了Car类,直接继承了SportCar类。实际上,许多类都间接继承了很多类,但仅直接继承于一个类,即每个类只有一个父类。
  Framework中的大多数类,包括前面已经介绍过的类和还没有介绍的类,都继承了其他类的一些功能。

  5.6 本章小结

  本章介绍了面向对象程序设计的基本概念,开始构建自己的对象。介绍了如何用所支持的属性和方法构建对象,并创建了一个类。之后给类添加属性和方法,并在应用程序中使用它。利用构造函数给新建对象赋予初值,并介绍继承和多态两个重要的概念。

  练习五

  1.什么是类?
  2.什么是对象?
  3.私有成员变量和公共变量之间有什么差别?
  4.什么是构造函数?为什么使用它?
  5..NET中的类都继承于什么类?