继承与接口
概述:了解在Microsoft Visual Basic .NET中的类继承与接口实现的区别。
目标
研究继承与接口使用背后的概念
学习何时使用类继承,何时使用接口实现
要求
为了充分利用本文,读者必须具备以下基础:
熟悉Microsoft® Visual Basic® .NET语言
熟悉Microsoft Visual Basic 6.0
基本理解继承中的术语
目录
面向对象编程:为什么编程?
继承层次
创建和实现接口
比较类继承与接口实现
对象合成
与 Visual Basic 6.0的区别?
小结
面向对象编程:为什么麻烦?
只有一小部分Visual Basic 6.0程序员发现他们在构建Visual Basic 6.0窗体时需要创建类模块,而不是使用自动创建的窗体。另外,大多数的确在Visual Basic 6.0中创建过类模块的程序员,创建创建类模块的原因,只是因为要构建ActiveX®控件,并且不得不使用类模块才能做到。
在Visual Basic .NET中,就象在所有其它语言中一样,面向对象编程(OOP)不是一个选择,而是一种需求。每段代码都是某种类型,如类、接口、结构(结构是值类型,类似于Visual Basic 6.0中的用户自定义类型),或枚举值的一部分。甚至在Visual Basic .NET中看上去独立的过程实际上也是作为隐藏类的共享方法实现的。
为何OOP如此强大?为什么微软一直要求大家学习用OOP在Visual Basic .NET中编程?
处理复杂性与变化
OOP解决两个普遍的软件开发问题:处理复杂性和处理变化。利用OOP就容易设计和使用复杂的软件系统,并且易于修改这些系统而无需打乱它们。
Visual Basic 6.0程序员需要用ADO或DAO对象模型获得和操作数据,设想,如果每个操作都必须调用独立的函数,而不是使用对象(如记录集)的方法或属性,完成这样的工作会有多么艰巨。例如,如果要用ADO或DAO向列表框中装入数据,大约要编写10行代码。而直接使用ODBC API函数,大约要编写50行代码!可以看到,OOP方法更容易实现。
再例如,考虑微软Windows® API。如果要花大量时间调用Windows® API或使用其它面向过程的APIs,就会发现,为一个任务要调用哪个函数或过程,是多么容易出错、多么难记。因此,通过创建相关对象,把问题模型化,对程序员来说,要比使用一长列的过程或函数要友好的多。
编写和调试代码是困难的,但大多数程序员喜欢跟踪问题,设计也一种灵活方式解决,然后“玩弄”代码,直到一切都满足了客户需求。这是有趣的人的一部分,但并不是事情的结局。所有程序员都害怕的一件事是:不得不研究其他人编写的代码(或他们在很久以前刚开始编写代码时所写的代码),不得不设计也程序各个部分如何搭配,如何进行修改以满足新需求——所有这些都必须不能引起意想不到的后果。不幸的是,在大多数系统生命周期中,维护代码所消耗程序员的时间,比创建这些代码的时间还要多,这就是这些系统的结局。
通过清楚地分离暴露的公有类属性、方法和事件与类成员的隐藏实现,在对象中封装功能就支持进行修改。只要公有接口受到保护,私有实现就可以安全实现,这就是目标。下面将研究确保这个目标实现的方式。
继承与接口
除了通过在对象的方法和属性中封装功能,使复杂系统易于创建和修改外,OOP还支持类继承和接口实现,通过这种方式,新类就能安全地加入到现有系统中而不必修改现有代码。通常将这两种方法比作准备变化,研究每种方法何时是合适的,并讨论第三种方法——对象组合,此方面经常是最好的选择。
继承层次
在所有存在于Visual Basic .NET中OOP技术中,继承对来自Visual Basic 6.0的程序员来说是最不熟悉的,因为在Visual Basic 6.0中不存在继承。甚至是Visual Basic 6.0程序员乐于使用的非Visual Basic 对象模型,如ADO或office库,也很少需要理解继承。
通过继承可以创建新类,它是现有(基)类的变体,并且在任何最初调用基类的情况中可以用新继承类替换。例如,有一个名为SalesOrder的类,从这个类中派生一个名为SalesOrder的类:
Public Class WebSalesOrder
Inherits SalesOrder
现在任何使用SalesOrder类型对象的方法,都将保持不变,并且也能处理WebSalesOrder对象。如果SalesOrder有一个Confirm方法,那么就可以确保WebSalesOrder 类也有一个Confirm方法。但如果在SalesOrder类中Confirm方法被标记为可重载的,那么就可以在WebSalesOrder类中创建新的Confirm方法来重载原有方法:
Public Overrides Sub Confirm
可能原始Confirm方法向客户发送传真,而WebSalesOrder 类中的Confirm方法使用电子邮件。重要的是,一个最初被设置为可以接受SalesOrder类型对象的方法,现在如果向它传递WebSalesOrder对象,那么此方法也能正常运行,并且该方法调用对象的Confirm方法后,客户将收到电子邮件而不是传真。旧代码不需要做任何修改就可以调用新代码。
另一方面,假设有一个Total,对两个类来说其运行情况完全相同。派生类WebSalesOrder不需要对这个方法作任何修改——它的实现将自动从基类继承,并且可以调用任何WebSalesOrder对象的Total方法,使此对象的行为就象SalesOrder对象一样。
多态性
多态性,或可替代性,是继承是明显的一个优点:不论何时创建了派生类对象,在使用基类对象的地方都可以使用此派生类对象。WebSalesOrder对象 "is a" SalesOrder对象,WebSalesOrder对象必须能实现SalesOrder对象的所有功能,即使是它以自己独特的方式。
多态性很多OOP优点的关键因素,在本文后面可以看到,它不仅存在于继承中,还存在于接口中。利用多态性,不同类型的对象就可以处理交互时使用的一组通用消息,并且是以它们各自的方式进行。
订单确认代码不需要知道如何执行确认,也不需要知道被确认订单的类型。它只关心它是否能够调用所处理的订单对象的Confirm方法,它要依赖此对象处理确认细节:
Public Sub ProcessOrder(order As SalesOrder)
order.Confirm
在调用ProcessOrder过程时,需要向它传递SalesOrder或WebSalesOrder对象,传递任何一个对象程序都能运行。
虚方法和属性
只有派生类重载基类方法时才用到虚拟,虚拟可以是继承中最具神秘性的概述。其神秘性在于.NET 运行时自动找到并运行被调用方法或属性的最特殊实现。
例如,调用上面示例中的order.Confirm方法将会调用WebSalesOrder类的Confirm方法,此处SalesOrder.Confirm方法被重载了。然而,调用order.Total方法则会调用SalesOrder类的Total方法,因为在WebSalesOrder类中没有创建专有的Total方法。
抽象类和方法
抽象是包含了至少一个必须被重载的方法或属性的类。例如,创建了一个没有实现订单确认的SalesOrder类。因为不同类型的订单必须以不同的方式确认,这样就要从SalesOrder类派生类并重载Confirm方法,提供它自己的实现。
这意味着永远不能创建SalesOrder对象。相反,只能使用派生类创建对象,填充如何执行订单的细节。由于这个原因SalesOrder类将被标记为MustInherit:
Public MustInherit Class SalesOrder
Public MustOverride Sub Confirm()
Public Sub Total()
'计算总数的代码
End Sub
如果必须重载所有类成员,那么这个类就被称为纯抽象类。例如,SalesOrder类可以