什么时候使用继承2007-05-24 本站 继承是一个非常有用的概念,但是却很容易被用得不合适,通常用接口来实现可能会更好,本文的目的就是使用户懂得怎样更好的使用类的继承。
当遇到如下情况时,继承将是一个好的选择:
(1)简化一个低等级的不使用类的API函数
(2)从基本类中得到重用的代码
(3)需要对不同的数据类型使用相同名称的类和方法
(4)类的层次相当,最多4到5级,而且不仅增加一级或两级
(5)通过改变一个基本类,就改变所有的派生类。
1.简化一个低等级的不使用类的API函数
类库使得一系列的函数的调用更加合理,特别是当调用的这些函数不是能类的形式组织的时候,举一个例子,就象那些为简化在窗体中画图表的API函数而设计的类一样,这种类库在有些时候被叫做"Wrapper"。这些把API函数包括起来的类库通常可以简化操作,不过这样做并没有企图把API函数的一些基本特性给隐藏掉。Windows Graphics API函数有很多,但用户并不需要了解它底层的一些变化。用户在设计类的时候也是这样,必须考虑类库的用户对底层的了解程序。实际上,很多类库都是根据API函数来组织的,通常它们有两个趋势:使得API函数对高级用户更加方便,或者通过高度的概括,把这些底层的信息隐含起来,这意味着把Windows API函数的具体信息对用户封装。
但是这里又有一个隐含的关于开发类库的问题,被打成包的类导致了非常多的文字说明,把API简化得越多,需要的文字说明就越多。经常性的,几百个API函数被组织成一打或一百个类,每一个类具有几十个或几百个重叠的属性、方法和事件,整理或者浏览这个类将是工作量非常大的一件事。
设计和维护一个把各种API打包的类库,是一件工作量非常大的事情,设想现在要写一个类库来简化一个为Web服务的Html的网页的生成过程,这就需要先考虑一下自己是不是需要知道"Html";是不是需要把这些诸如"tags"或"angle brackets"的概念隐含掉;是不是向用户显示一些诸如字体等这些属性;是不是只要能处理Html就行了,还是需要处理XMl,能不能叫XHtml?等等。
假设一个基本类含有一个MustOverride方法叫做ParseHtml,然而当需要支持XMl时,就必须把Html支持替换掉,此时需要把方法ParseHtml清空,惟一的选择是建立一个ParseXMl方法,但不能把ParseHtml方法移除掉,因为当把该方法移除以后,就会导致Html业务的丧失,因为整个类是分布的,用户必须让ParseHtml和ParseXml方法同时存在,但这让内存和运行时间效率都减低了。
诸如这种问题在网络开发环境还是存在的,用户可能是过早设计了Html类库,也许一个基于接口的系统可以更好的解决这个问题。它让用户建立一系列的Html接口,当在确认XHtml或者XML是更好的基底时,就可以放弃或改善Html接口。
2.从基本类中得到重用的代码
使用继承的一个重要的原因就是为了代码的重用,然而这也是最危险的。正是因为代码的重用,即使是设计的最好的系统,有的时候也会出现一些设计者难以预料的改变。
一个经典的表示代码重用的高效率的例子就是一个数据管理库,设想现在有一个大型的应用程序,用来管理几个在内存中的清单,另一个是从顾客的数据中拷贝来的清单,该数据结构可以如下所示:
Class CustomerInfoPublic PreviousCustomer As CustomerInfoPublic NextCustomer As CustomerInfoPublic ID As IntegerPublic FullName As String"Adds a CustomerInfo to listFunction Insertcustomer As CustomerInfo...End Function"Removes a CustomerInfo from listFunction DeleteCustomer As CustomerInfo...End Function"Obtains next CustomerInfo in listFunction GetNextCustmer As CustomerInfo...End Function"Obtains previous CustomerInfoFunction GetPreCustomer As CustomerInfo...End FunctionEnd Class可能还有一张顾客的采购的东西的卡片清单,如下:Class ShoppingCartItemPreviousItem AS shoppingCartItemNextItem As ShoppingCartItemProductCode As IntegerFunction GetNextItem As ShoppingCartItem...End Function ...End Class
从这里,就可以看见类的结构和模式,都有相同之处--insertions(插入)、deletion(删除),以及traversal(浏览),只不过是对不同数据类型进行操作而已,显然,要维护这两段几乎具有相同函数的代码很没有必要,又一个显然的解决方案就是把对清单的处理抽象出来,做成一个它自己的类,然后,就可用这个类派生出不同数据类型的子类,如:
Class ListItemPreviousItem AS ListItemNextItem As ListItemFunction GetNextItem As ListItem...End Function ...End Class
这样的话,只需要对ListItem类调试一次,而且用户只需要把它编译一次以后,就不再需要管理关于清单管理的代码,用户只需要使用它就可以了:
Class CustomerInfoInherits ListItemID As IntegerFullName As stringEnd ClassClass ShoppingCartItemInherits ListItemProductCode As IntegerEnd Class
3.对不同的数据类型使用相同名称的类和方法
一个决定是否要使用继承办法就是是否有以下问题:
(1)是否需要对不同的数据类型进行相似的操作;
(2)需不需(想不想)访问程序源代码;
举一个非常具有代表性的例子,就是画图的软件包,假想它可以画圆、线和长方形。一个非常简单的而且效率很高的方法就是不需要使用继承,而用Select Case语句来实现,如下:
Sub Draw(Shape As DrawingShape,X As Integer,Y AS Integer,_Size AS Integer)Select Case Shape.TypeCase shpCircle"Circle drawing code hereEnd CaseCase shpLine"Line drawing code hereEnd Case...End Sub
但是这样处理的话将会出一些问题,将来如果需要添加画椭圆功能的话,那么就必须重新改写代码,也许最终用户不需要访问程序的源代码,但是可能画一个椭圆还需要一些参数,因为椭圆需要的参数是一个长半径和一个短半径。但是这些参数又和其他图形的参数无关,如果现在又要画一个折线(有多条线组成),则又需要加入一些其他的参数,而且这些参数又各其他的图形的参数无关。
继承就可以非常好地解决这些问题,一个设计得很好的类可以给用户留下一下很好的空间(MustInherit方法),那样的话,每一种图形都可以满足,但是一些类的属性(诸如XY坐标)可以作为基本类的属性,因为这个属性是每一种图形都需要的,如:
Class Shape Sub MustInherit Draw() X As Integer Y As IntegerEnd Class
其他的图形都可以继承这个类,如要画一条线,代码如下:
Class Line Extends Shape()Length As IntegerSub Overrides Draw"Implement Draw hereEnd SubEnd Class长方形的类,又应该如下表示:Class Rectangle Extends Line() Width AS Integer Sub Overrides Draw() "Implement Draw here End SubEnd Class
它的继承子类(例如:长方形)能够在图象类的二进制的文件的基础上继承,而不是在基础类的代码的基础上继承。
4.基类的方法需要改变
Visual Basic(以及其他的提供继承的编程语言)使得用户有能力重载基本类的方法,只要在派生类中可以访问的基类的方法都可以被加载(除了那些NotInheritable)。假如用户感觉基本类的DisplayError方法需要给出一个错误号,而不只是错误的描述,则用户可以在派生类中改变这种方法的行为,则派生类会调用它本身的方法,而不是基类的方法。
5.通过改变一个基本类,就改变了所有的派生类
继承的一个强大的作用就是当改变一个基本类的属性时,就改变了所有的派生类的相应的结构,但是,这也是一个危险的行为--有可能改变了自己设计的基类后,别人由该基类而派生出来的派生类就会产生错误。