文档库 最新最全的文档下载
当前位置:文档库 › 第40章 组件开发员的面向对象编程

第40章 组件开发员的面向对象编程

第40章组件开发员的面向对象编程

如果你曾经有过用C ++B u i l d e r编程的经验,那么你知道类也就是数据和代码,而且无论在设计时还是在运行时均可以操作类。由此可见,你已经是个组件用户了。

在创建组件时,你处理类的方式与应用程序开发人员处理类的方式是不同的。你总是努力将组件的内部对使用组件的应用程序开发人员隐藏起来。通过选择适当的祖先,可以将组件设计成只对外显露应用程序开发人员需要的属性和方法,再遵循本章阐述的其他一些准则,你可以创建能通用、可重用的组件。

在开始创建组件之前,应熟悉一些有关面向对象编程(O O P)的主题:

? 定义新类。

? 祖先、后代和类层次结构。

? 控制访问。

? 分派方法。

? 抽象类成员。

? 类与指针。

40.1 定义新类

组件开发员与应用程序开发人员的不同之处在于:组件开发员创建新类而应用程序开发人员操作类的实例。

类本质上就是一种类型(t y p e)。作为程序开发人员,总是与类型和实例打交道,尽管你没有使用这些术语。例如,创建诸如i n t那样类型的变量。类通常要比简单的数据类型复杂得多,但是它们的工作方式是一样的:通过给同一类型的不同实例赋值,可以执行不同的任务。

例如,创建有两个按钮的窗体,一个按钮标志为O K,另一个为C a n c e l,两个都是T B u t t o n类的实例,但它们的C a p t i o n属性被指定为不同的值,并且传给O n C l i k事件处理程序的句柄也不同,也就是说,你有两个行为不同的实例。

40.1.1 派生新类

有两个理由派生新类:

? 修改类的缺省值以避免重复编码。

? 在类中添加新特性。

每个理由的目的都是创建可重用的对象。如果你在设计组件时关心可重用性,那么以后的工作效率就会高一些。将类的缺省值设置为高可用性的值,但同时要允许用户定制它。

1. 修改类的缺省值以避免重复编码

大多数程序员都努力避免重复编码,因而当你察觉自己一次又一次地编写相同的代码时,你会把这些代码放到函数中,或者建立一个可以在多个程序中使用的例程库。对于组件来说同样如此。如果你发现自己在修改同一个属性,或调用同一个方法,可以创建新组件缺省地完成这些事情。

例如,假设每次创建应用程序时都会调用对话框执行特定的操作。虽然创建对话框并不难,但这是不必要的。你可以一次性设计该对话框,设置它的属性,然后封装成组件安装到组件面板。将该组件设计成可重用的,不仅避免了重复编码,而且促进了标准化以及降低了对话框重复创建时的出错概率。

第4 7章给出了改变组件缺省属性的例子。

注意如果你只想改变已有组件已发布的属性,或者只想存储组件(或一组组件)的某个事件

处理程序,你可以创建组件模板使这些事情变得容易些。

2. 在类中添加新特性

创建新组件的一个常见理由是想添加已有组件所没有的特性,当做这些事时,要从已有组件或者诸如T C o m p o n e n t、T C o n t r o l这样的抽象基类派生新组件。

从最接近所需特性的类派生新组件。可以在类中添加特性,但不能去掉它们。因此,如果已有的组件类含有不想要的特性,应从它的祖先派生组件。

例如,如果想在列表框中添加特性,可以从T L i s t B o x派生组件。但是,如果不想要某些标准列表框的特性,应从T C u s t o m L i s t B o x派生组件,它是T L i s t B o x的祖先。这样,就可以创建(或使其可见)你需要的特性,并添加的新特性。

第4 9章给出了定制抽象组件类的例子。

40.1.2 声明新组件类

除了标准组件之外,C ++B u i l d e r还提供了许多抽象类用于派生新组件。表3 9-1列出了可用于创建组件的类。

为了声明类,在组件的头文件中添加类声明。

下面是个简单的图形组件的声明:

不要忘了包含PA C K A G E宏(在S y s m a c.h中定义),该宏使得类可以引入或引出。

已完成的组件声明包括属性、数据成员和在后一个大括号之前声明的方法,但是空声明也是合法的,可以作为添加组件特性的起点。

40.2 祖先、后代和类层次结构

应用程序开发人员理所当然地认为每个控件都有To p和L e f t的属性用于确定它在窗体中的位置,对他们而言,所有的组件都是从一个共同祖先T C o n t r o l继承这些属性的事实没有什么值得关心之处。但是,当你创建组件时,你必须知道你的控件继承的各样特性,这样你才能利用它们而无需重建它们。

派生你的组件的那个类被称为组件的直系祖先。每个组件都继承它的直系祖先以及直系祖先的直系祖先,依此类推。组件继承的所有类都被称为祖先,该组件则被称为祖先的后代。

总之,应用程序中的祖先-后代关系组成了一个类层次结构,该结构中的每一代都比它们的祖先拥有更多的特性,因为它们不仅继承祖先的特性,而且自己添加了新特性或是重定义了已有的特性。

如果你不明确指定直系祖先,C ++B u i l d e r将从缺省的类TO b j e c t派生你的组件。所有对象层次结构中的类有一个最终祖先,就是TO b j e c t。

选择从哪个类派生组件的基本原则很简单:选择最能满足你的需要、且没有任何你不想要的特性的对象。因为,你可以添加新特性,却不能删除你继承的特性。

40.3 控制访问

属性、方法和数据成员的访问控制(也称为可见性)有5个等级。可见性决定了什么代码可以访问类中的什么部分。你通过规定可见性为你的组件定义接口。

表4 0-1显示了可见性的等级,从最严格限制的等级到最大可访问等级。

表40-1 对象内的可见等级

可见性含义用处

私有(p r i v a t e)只有类代码可以访问隐藏实现细节

保护(p r o t e c t e d)类和它的后代的代码可以访问定义组件开发员接口公共(p u b l i c)所有代码都可以访问定义运行时接口

自动化(__ a u t o m a t e d)所有代码都可以访问,且生成自动( A u t o m a t i o n)只用于O L E自动化

类型信息

发布(__ p u b l i s h e d)所有代码以及从对象观察器可以访问定义设计时接口

40.3.1 隐藏实现细节

类中p r i v a t e声明的部分对于类外部的代码是不可访问的,除非该函数是该类的友元。类的私有部分最大的用处是对用户隐藏类的实现细节。由于类的用户无法访问私有部分,你可以改变类的内部实现而不会影响用户代码。如果你没有明确地指定数据成员、方法和属性的访问控制,则它们是私有的。

下面有个例子在两个部分中显示如何声明p r i v a t e数据成员以禁止用户访问。

第一部分是个窗体的单元,由一个头文件和一个. C P P文件组成,在窗体的O n C r e a t e事件处理程序中一个私有数据成员被赋值。由于该事件处理程序在T S e c r e t F o r m类中声明,因此不会产生编译错误。

下面是对应的. C P P文件:

例子的第二部分是另外一个窗体的单元,该单元试图对S c r

e

t F o r m 窗体中的F S e c r e t C o d e 赋值。该单元的头文件如下:

下面是对应的. C P P 文件。由于O n C r e a t e 事件处理程序试图设置S e c r e t F o r m 窗体的私有数据成员,因此编译给出错误消息‘T S e c r e t F o r m ::F s e c r e t C o d e ’is not accessible 。

使用H i d e I n f o 单元的程序可以使用T S e c r e t F o r m 类型,但不能访问类中的私有数据F S e c r e t C o d e 。556计计第五部分创建定制组件下载

40.3.2 定义组件开发员接口

类中用p r o t e c t e d声明的部分只允许类自身或它的后代访问。

可以用p r o t e c t e d声明来定义组件开发员的类接口。应用程序单元不能访问这些部分,而派生类却可以。这意味着组件开发员可以改变类的工作方式而应用程序开发人员却不能察觉这些。

40.3.3 定义运行时接口

类中p u b l i c声明部分对任何代码都是可以访问的。

由于在运行时,任何代码都可以访问p u b l i c部分,因此它可以用于定义类的运行时接口。运行时接口对那些在设计时没有意义或适当取值的项目(诸如依赖运行时输入的属性或只读的属性)非常有用。你提供给应用程序开发人员的方法必须是公共的。

下面有个例子显示将两个只读属性声明为组件的运行时接口:

下面是. C P P文件中的G e t T e m p F a h r e n h e i t方法:

40.3.4 定义设计时接口

在p u b l i s h e d中声明的部分是公共的,且产生运行时类型信息。运行时信息使得对象观察器可以访问类的属性和事件。

由于它们可以显示在对象观察器中,因此p u b l i s h e d部分定义了类的设计时接口。设计时接口应包含所有的用户想在设计时定制的东西,但不能有任何依赖运行时环境的属性。

设计时接口中不能有只读属性,因为应用程序开发人员不能直接给它赋值。只读属性应是p u b l i c的,而非p u b l i s h e d的。

下面有个例子显示属性Te m p e r a t u r e,由于它是p u b l i s h e d,因此它可以在设计时显示在对象观察器中。

40.4 派发方法

作为术语,派发(d i s p a t c h)用于描述当类方法被调用时,应用程序如何确定哪个方法应被激活。当

调用类方法时,它看上去类似一般的函数调用。类有两种类型的调度方法,它们是:

?常规(而不是虚拟)方法

?虚拟方法

40.4.1 常规方法

只要你没有专门将类方法声明为虚拟的,也并非重载基类的虚拟函数,那么该方法就是常规方法。编译器能在编译时精确确定常规类成员的地址,这就是众所周知的编译时连接。

派生类继承基类的常规方法。在下面的例子中,一个D e r i v e d类型对象调用了R e g u l a r()方法,就好像它是自己的方法一样。在派生类中声明的方法,如果它的名字和参数均与祖先的方法相同,则该方法将替代祖先的方法。在下面的例子中,当d ->A n o t h e r R e g u l a r()被调用时,实际调用的是D e r i v e d的方法。

40.4.2 虚拟方法

与常规方法在编译时连接不同,虚拟方法是在运行时连接。C ++的虚拟机制依靠被调用的方法所在的类来确定被激活的方法。

在前面的例子中,如果通过D e r i v e d对象指针调用F u n c t i o n T w o(),那么D e r i v e d::V i r t u a l()函数将被调用。虚拟机制动态检查在运行时传递的对象所属的类类型以确定适当的方法。但是,当 b -> A n o t h e r R e g u l a r()调用常规方法时,实际被调用的总是B a s e::A n o t h e r R e g u l a r(),因为A n o t h e r R e g u l a r()的地址是在编译时确定的。

为了声明虚拟方法,要在方法声明前加上v i r t u a l关键字作为前缀。

当编译器遇到v i r t u a l 关键字时,会在类的虚拟方法表(V M T )中创建一个入口。V M T 包含该类所有虚拟方法的地址。这个查找表用于在运行时确定 b - > V i r t u a l ( )应调用的是D e r i v e d : : V i r t u a l ( ),而不是

B a s e ::V i r t u a l ()。

当从已有类派生新类时,新类会得到一个V M T ,里面不仅有自己声明的虚拟方法,还有从祖先那儿得到的入口。另外,后代能重载它继承的任何虚拟方法。

重载方法

重载方法意味着扩展或重定义祖先的方法,而不是取代它。为了重载方法,要在后代中重新声明该方法,注意确信参数的数目和类型是一样的。

下面的代码是两个简单组件的声明。第一个声明两个方法,每个的调度方法不同。另外一个从第一个派生,替代了它的非虚拟方法并重载了它的虚拟方法。40.5 抽象类成员

如果方法是在祖先中用a b s t r a c t 声明,那么在使用新组件之前,必须在某个后代中重新声明(即封装,s u r f a c e )并实现它。C ++B u i l d e r 无法为含有抽象成员的类创建实例。关于封装类的继承部分,可以参见第4 1章和第4 3章。

40.6 类与指针

每个类(同时也是每个组件)实际上是个指针,当将类作为参数传递时,这一点非常重要。通常,应按值传递类而不是按引用传递,其中的缘由就是类实际上是指针,而指针就是引用。按引用传递类即是传递引用的引用。第4 0章组件开发员的面向对象编程计计559

下载

相关文档