谈组件技术(一)
何谓组件技术?组件技术有什么作用?为什么要应用组件技术?如何应用组件技术?我们现在都知道什么?我们现在应该做什么?又能做什么?当这些问题缠绕在心头时,也许您自己也承认,不得不学一学组件技术了,其码要了解组件技术(组件技术本文以后将以组件代替)。那么现在问一问自己,你是如何看待组件的?你心目中的组件是一个什么样子呢?感觉很好说,也许每个编程的人员对组件都有一个轮廓的概念,是的,Windows平台已经用了快十多年了,怎么能没有听过组件?从大的方面而言,当MS应用了OLE的时候,组件已经在慢慢的发展着,从最初的OLE 到COM 到CMO+甚至到了现在的.Net,无处不见组件的存在,然而这仅仅是一个轮廓的影响,我想,在学习组件的时候,首先应该明白一些基本的概念。而写这篇文章的最终目的就是要回答开篇是提出的几个问题以及教您如何写组件(本文将以编写一个ocx组件、应用MTS组件、COM+组件结束,而对于COM+ 就是MTS的更新版本所存在的某些歧义也将给于解释)。
何谓组件技术?具体死板的概念在各种书籍上有很多的定义,组件技术就是利用某种编程手段,将一些人们所关心的,但又不便于让最终用户去直接操作的细节进行了封装,同时对各种业务逻辑规则进行了实现,用于处理用户的内部操作细节,甚至于将安全机制和事物机制体现的淋漓尽止。而这个封装体就常常的被我们称作组件。或许这个定义有些勉强,但这样的解释对现在的你是有帮助的,而这个封装的过程中,编程工具仅仅是充当了一个单纯的工具罢了,没有什么实际的意义,也就是说为了完成某一规则的封装,可以用任何支持组件编写的工具来完成,而最终完成的组件是与语言本身已经没有了任何的关系,甚至可以实现跨平台。对我们而言,它就是实现了某些功能的、有输入输出接口的黑匣子罢了。
组件有什么作用?这个问题似乎有些笼统,试着想一想windwos何以实现如此强大的生产力?而在它的背后到底有什么在服务着?一句话:组件就是windows的灵魂,脱离了组件,windows系统将不再如今天一样如日冲天,windows如此,Unix也同样是如此,作为一个操作系统,它所完成的功能无不提体着组件的服务,一个很轻松的Copy – Paster都要靠DDE来支持,而DDE就是一种组件服务对象,而具体到某一个细节,如今的大型ERP靠的是什么?多层系统又靠的是什么?组件!组件封装了系统运行的各种规则甚至运行环境,而组件又何以完成如此多的服务?这儿就不得不提起组件对象,所谓的组件对象是组件的一个集合,而这个集合又并非是随意性的组合,必须要考虑到组件对象中的各个组件的协调功能,虽然,在理论上讲,一个组件对象里的各个组件应该是互不干涉、互不影响的,但是这并不意味着组件对象就是一个无协调的组件的集合,我们必须理解,通过访问一个组件对象中的某个组件,就有可能访问此组件对象中的另外的组件以此循环下去.而这些都由谁来管理?肯定是组件对象,可以这样理解:组件有其自身的规则实现,而规则的实现又提体到了接口的实现,但是组件对象本身也是一个组件,它也有业务逻辑规则需要处理,它也要起到所集合的组件的协调。如此一来,可以能过某一个组件对象来协调的实现一些、一部分的业务逻辑规则!而对于应用者来说,这一切完全没有必要去得知,甚至是没有意义的。
为什么要应用组件技术?或许你会说,我们通过编程的手段同样可以处理一些简单的或稍微复杂的业务规则,的确,不否认,通过编程的手段我们可以实现如组件对象一般实现的规则处理。然而如果仅仅是因为此我们就说组件对象或组件和我们平时的编码是一致的,那么现在可以明确的告诉你,这是一个很糟糕的想法。使用组件技术的的目的是实现各种规则{你想要实现的规则}的实现,而且组件对象还将从更广阔的方面来考虑,它能将一个大型的分布式系统进行统一的规划、合理的处理冗余、安全、平衡负载……等单纯的编程手段不能实现的功能,这就是我们要应用组件的一个很重要的原因。再者,组件对象不是普通的可执行文件,更不是将各种规则定死在其内部,它可以很平滑的实现自身的升级、扩展(前提:不大量的更动接口),举一个很简单的例子,当我们发现某项业务逻辑规则已经很陈旧的时候,我们不得不用新的业务逻辑规则去替换它,而这个替换过程将会提现出组件对于普通的.Dll文件或是.Exe可执行文件的具大差别。当我们需要进行更新的时候,对于组件对象而言,在最理想的情况下用户可以一边进行组件对象的应用,一边无知觉的接受新的组件技术,而试问一个Dll文件或是某一个可执行文件可以达到这样的效果吗?答案是否定的。如果你说这些理由还不够的话,我可以举出很多的应用来说明组件应用的必要性,然而,理由实在太多,也没有必要一一列举,你可以参考其它的技术资料或是在平时的操作中去发现。
如何应用组件?在这点上或许我们更关心的是我们如何通过编程的手段来实现组件,在开篇的时候已经提起过,组件就是利用某种编程的手段来封装业务规则,而且也强调了语言在此处仅仅是一个工具罢了。但你可以完整的写出一个组件的时候,那么对于其应用就会更明确,对其给工作带来的效率而惊叹不已。那么到底如何应用组件技术?组件技术属于高组的应用部分,它可以从系统的底层作起,一直到我们可以感觉的明显出来的功能的封装。而在此过程中,我们就是要通过自己熟悉的工具来写一个好的组件对象或是组件。
我们现在都知道什么?虽然组件技术属于高级编程范畴,但是只要它是可以编程实现{又有那一种技术不可以实现呢?}的,我们就可以去实现一个组件。并且我们已经知道了组件的应用、作用,那们我们现在所应该做的就是熟悉一门开发工具,所谓的工欲善其事,必先利其器就是说我们只要很好的掌握了一个工具才有可能跨进组件技术这个大门(请不要在组件技术是语言无关性上和此处的表达方式进行纠缠,再如何的语言无庆性也必须要靠工具去实现,不是吗?),否则就算对组件技术理解的再透彻也只能站在门外徘徊。所以我们现在知道的无非就两点:组件和我们所掌握的工具。
我们现在应用做什么?又能做什么?没有基石的大厦必将会倒塌,我们没有坚实的基础做后盾,那么我们所写出来的组件必将也是一些垃圾!或是不成形的玩具罢了。或许你对组件很了解,而且也很明白FrameWork的设计,但是这一切都是建立在对其内核不理解的基础这上,只能说这是一种空白的设计,因为你将无法的切身体会的那种设计模设将会给你的组件带来质的飞跃,组件如此,其它也如此,就如一个好的项目经理需要有灵敏的思维和高超或是相当不错的技艺一样,否则,别人只会认为他在空谈自己的构想。所以我们现在应该做的就是充分的理解组件的应用之处、组件自身的规则以及我们的开发利器。这就是我们应该做的而且也是我们目前唯一可以做的。
当我们对组件有了一个基本的轮廓或是比较清楚的影响时,下一步就是来将我们的工具与组件结合起来。每一个开发工具都有其自身的特点,不同的语言虽然可以实现某些相同的功能,甚至被人们所说使用何种开发工具都一样的言辞所一概而论,此时我们就应该意识到这点是错误的,因为开发工具都有其利有其弊,应用其利是我们应该做的(并非是要大家对弊端弃之),此为其一,另外我们也必须要知道用什么样的技术所实现的更能给我们的组件带来效益上的飞跃、冗余上的放心、负载平衡上的可靠等。到现今为止,我们在熟悉我们的开发工具的同时还需要将面向对象编程的思想深深的渗透。为什么要这样说呢?.Net的Framework的设计模式给了我们很大的忠告,一个组件其实就是一个工程,一个项目,而实现工程、项目的最好方法就是充分的利用OOP思想,当然,如果你要是能确定你此处的版本是最终版本的话,倒也无妨,无非就是在此版本中的编码系统上复杂一些罢了。可以看到出OOP编程对于我们写组件有很大的帮助,如果您还不明确的话,不妨举一个很简单的例子:通过OOP可以充分的减少复用程度,提高可扩展程度。而一个组件将作为众多个终端的应用服务器,我们就一定要考虑到其执行效率、可扩展性等众多特性。这是相互的一个过程。所以在这之后,我会先对OOP做一个简单的概括{各位都是做OOP编程的,所以此处仅仅是作简单的提起,省去N个字}.
让面向对象编程渗透到我们的每一个实现的细节中。
{因为此处要设计到一些例子,而我所熟悉的工具又是Delphi,所以,此处将以Delphi作为OOP的对象来讲}
面向对象思想的三大核心内容是封装,继承,多态。那么我们就对这三点进行分析,并就其中的一些例子进行点评。
在此处省去了分析类、对象这两个概念,但应该明白的是对象,作为类实例,从某种意义或是一般而言,它就是一个指针,当然,如果此处有人一定要强调Delphi是一个引用/对象模型,而并非是对象指针的话,我不以辩解。
封装:组件对象要封装组件,而组件又要封装接口,最终接口还是需要封装其所实现的业务逻辑规则,所以,单一的从OOP的这一点上来考虑的话,组件对象和OOP就应该有某种分不开的关系。封装主要提体在两个方面:类封装、对象封装,具体的细节可以参考相关资料。
封装就是一种UI分离,为什么我会这样说呢?提到封装,也许我们最容易想到的就是黑匣子。的确,我也承认封装就是一个基于黑匣子的实现方式,而且也是相当恬当的一种比喻,如何说呢?因为封装也一样有两个“界面”,一个是活的,一个是稳定的,而所谓的活就是封装内部所处理业务规则的实现方法,如果不去计较其效率的话,我们可以用任何的手段去实现其内部的功能,可以进行随意性修改(此处的随意性修改仅仅是为了体提它的“活”而给出的。)但是无论其是如何的活,如何的随意性,它都有一个最终的目的,就是为了实现一个稳定的“界面”,内部的内容可以进行改动,而稳定的接口是不应该随意更动的。类如此、对象如此,一个接口、组件、组件对象更是如此。单纯的从一个项目而言,这种封装就是一种UI分离,不要固执的认为UI分离就是将用户操作和逻辑规则处理放在了两个“物理”上很远的操作就是封装,如果这样认为,我只能说你的OOP理解的不好。此处给一个例子来进行分析:
TCustomForm = class(TScrollingWinControl)
private
FActiveControl: TWinControl;
FFocusedControl: TWinControl;
function GetMDIChildren(I: Integer): TForm;
function GetMonitor: TMonitor;
……
public
constructor Create(AOwner: TComponent); override;
procedure AfterConstruction; override;
……
end;
想说明的是此处的private 就是我们刚刚所说的活的部分,它的内部可以进行任何的改动的,但是,它必须要保证最终的Public 是稳定的。这就是一种封装的提体,也是一种UI操作的实例。同时我们也应该注意到,正是因为封装性,所以就要求我们在考虑稳定部分的时候做一些工作,当一个项目投于应用之中之后,我们就没有办法再去更改这些稳定的部分,因为这些部分是直接于用户进行关联的,用户的操作就是对急定部分的操作,如果一但改动了某一个用户正在使用的稳定部分的属性,其后果就是项目的重新规划、开发。总之一句话:封装可以隐藏实现细节,使得代码模块化,实现代码的可复用性。不知道封装的阐述各位是否明白?如果有任何的疑问可以与我联系,欢迎您和我一起探讨。
继承 :封装隐藏了实现细节,那么继承呢?继承是为了扩展已经存在的模块(如类、接口),同样它也是实现了代码的可复用性,在封装部分已经强调了,封装就是将一个模块很明确的划分成了“活”的实现部分和稳定的实现部分,而我们也提起了,其实现之后并且进行了分发就不应该进行更动,特别是当一个项目已经投入到应用之后,再次修改稳定的部分将破坏了原有的初衷,继而将有可能造成整个系统成为垃圾,在接口中,提体的更明显,那么我们如何也不可能对于某一个模块永远的不进行功能的添充、修改等操作,我们如何办?修改其中的一个接口吗?肯定不行, 修改接口其实就是在说:我这个组件将不再为我的用户所服务,因为用户正在应用的接口我将重新进行规划,而且其它组件中有可能调用此接口的接也将不再使用,试想一下,这种结果是什么?{说明:对像可以实现多个接口,而一个组件又可以包含多个实现接口的多个对象,同时一个组件对象又是多个组件的集合}。怎么办?废弃这个组件对象或是项目吗?那么用户怎么办?老板允许我们这样做吗?肯定不成。而此时就是用继承的时候了。如接口A中的某一个方法要进行改变(此处的方法其实就是属性!),同时要增加另外一个属性,有两种方法,重新写一个接口,另外一种就是继承于接口A,让接口B作为接口A的派生接口,它可以实现所有接口A中的可继承属性,同时又可以覆盖掉某一个属性而用另一种规则进行实现并且也可以再新添加自己的方法、属性。而这一切对于用户来说是完全透明的,他们不知道你做了些什么,更不知道,它应用的某个接口已经被替换掉了(可以替换吗?注意Delphi中的As的用法).
多态:在理解了封装和继承的同时,再让我们来看一看多态,如果要对多态进行形象、明了的阐述,将会花太多的篇幅,而现在关于多态的阐述又很多,您可以参考一些资料进行理解,此处仅仅进行简单的说明:多态是OOP中三个基本概念中最不好理解而且也是最有趣的部分,我们提到了封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的本质就是将子类类型的指针赋值给父类类型的指针(在OP中是引用,实质上也是一种指针),只要这样的赋值发生了,多态也就产生了,因为实行了“向上映射”。
符加部分
{
也谈多态
封装、继承、多态构成了OOP的三大基本核心,封装、继承在之前我们曾作过介绍,而多态没有进行详细的介绍,在此给以补充,以完成OOP的三大主题来结束OOP的那一块。关于多态在很多资料上都有介绍,而且介绍的非常精彩,那么就让这篇文章再来进行一次总绍吧。
首先说点废话,以下的介绍以及实例中,我将以Object Pascal作为讲解的工具,而多态实质上是OOP的,不属于某一个工具的,它是一种信仰。在此处虽然以Objec Pascal来进行描述,其实可以扩展到任何的语言中。言归正转,继续我们的话题。
什么是多态?多态的本质是什么?多态和封装、继承有什么关系?它在OO中站有什么样的地位?多态给我们的工作代来了什么?本文就对这些问题加以阐述,在阐述的过程中来理解多态,认识多态,应用多态。
什么是多态?似乎没有一个统一的定义来规范多态,或许以我们自己的理解方式来解释多态更为贴切,在此我们不引用一些术语来进行定义,就初学者也可以理解的方式来形像这个定义:多态,顾名思议,就是多种形态,而这种多种形态又具体又体现在了什么地方?可以这样理解,我们可以用一种轮廓的物体来描述不至一种的多个物体,而至于我们到底要描述那个物体的状态,对于我而言不很重要。为什么要这样说呢?多态就是给我们体供了这样一种机制,我们对它仅仅是一个形式上的声明、定义,而具体的实现上的细节我们没有必要在此进行关心。或是具体的实现细节将交给别的东西去实现。我们所声明的这个基类从某种意义上而言只是一种定义。就如接口一样,没有实现部分,说到这儿,不得不说多态的本质了。多态的本质就是基类提供一系列的虚拟方法,如果试图去实现这个基类的话,将被编译器告之这样是行不通的。多态性的完整提体是交对象和他的子对象共同完成的一个思维方式。 多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。对于用户而言,他们所关心的只不过是那些没有实现细节的父类所提供的虚拟方法罢了。如果以接口的话来讲就是:我们不去关心,也没有必要去关心接口的实现细节,我们所关心的仅仅是接口为我们提供的方法来完成我们的功能,而至于接口以何种方式来实现我们所需要的不同功能甚至是一个特定方式的多个功能都是其内部的事。不知道这样的解释是否让大家感觉很模糊,在理解上是否感觉不能很好的接受。请继续往下看。
谈到多态,我们首先要明白,对于多态而言,其父类提供了很多的虚拟方法,所以我们没有办法去直接实现这个父类,只能通过它的派生类去覆盖其提供的虚拟的方法,所以这就产生了多态。我们都知道,一个类可以被多个子类所继承,而每个子类所实现的方法又不一至,或是根据它们自己特有的特点对一个特定的方法的实现上因为类差别而存在着差别,但是我们完全可以通过父类去模糊化这些操作。对子类的方法的调用完全都可以通过基类去实现。由此而言,如果此基类没有被派生,那么这个类是完全没有存在的意义的!只有它被某一个子类派生了,那么它就正真的有存在的意义了。这种意义又提体在什么地方?或我们以什么手段来让这种意义成为焦点呢?这就是我们需要进一步走进多态的原因,如下所示:
TA = Class
Public
Procedure A ; virtual ; abstract;
Procedure B ; Virtual ; abstract;
Procedure C ; virtual ; abstract;
end;
当我们声明了类TA时,可以看到它有三个方法,而且也注意到了这三个方法都是完全虚拟的,如果此时我们试图实例化这个TA,将会没有任何的意义的,因为我们无法去实现类TA给我们提供的各种方法,或者说编译器将不提供空间/机制来让我们实现这些虚拟的方法。只有通过其子类进行覆盖这些虚拟方法才可以正真的将它们提体出它的作用。而覆盖就是说子类重新的定义、实现了基类的方法。如:
TB = Class(TA)
Private
B_Str : String;
Public
Constructor Create(Value : String);
Destructor Destroy();override;
{这是覆盖。设么地方可以用override?当他积累相对应的方法是虚拟的或是动态的时候才可以用override}
Procedure A ; override;
Procedure B ; override;
Procedure C ; override;
end;
TC = Class(TA)
Private
C_Str : String;
Public
Constructor Create(Value : String);
Destructor Destroy();override;
Procedure A ; override;
Procedure B ; override;
Procedure C ; override;
end;
这里有一个初学者或是不太注意语法的人经常混淆的概念。覆盖(override)和重载(overload)。上面说了,覆盖是指子类重新定义父类的虚函数的做法(inherted的用法是什么意思你真正的明白吗?)。而重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。如下:
Procedure OpenSQL(Sender : TADOQuery;SQLStr : String); overload;
{当重复时,返回为False,否则返回为True}
Function OpenSQL(SQLStr : String) : Boolean ; overload;
Function ifrepeat(TableName,FieldName,FieldValue : String;IntValue : Integer = 0):Boolean;overload;
Function ifRepeate(TableName : String;FieldName , FieldValue :
Array of String) : String ;overload;
可以很清晰的看到上边的代码就是一个子类覆盖了基类的虚拟方法的过程,而此时还不能很明确的提体出来多态的特性,或是说多态的本质问题还没有提体出来,并且您可能于晚期绑定也不是很明白,下边将给以解释。
当我们从抽象类TA派生出了类TB和TC,并且,子类也覆盖了基类的虚拟方法,现在如何应用这些方法呢?我们将以什么样的形式去调用这些方法呢?这才是多态最值得焦点的时刻,也是最能体现多态的本质的地方,之前已经给您灌输了多态的本质就是将子类类型的指针赋值给父类类型的指针,只要这样的赋值发生了,多态也就产生了,因为实行了“向上映射”。何以这样说呢?请继续我们的实例,假设它们的实现方法分别如下:
{ TC }
procedure TC.A;
begin
inherited;
{inherted的意思就是把相应的消息交给其父类去处理,他不属于我们讨论的范畴}
ShowMessage('TC' + C_Str);
end;
……
{ TB }
procedure TB.A;
begin
inherited;
ShowMessage('TB' + B_Str);
end;
……
现在我们就可以通过父类来来对子类进行调用,而这个过程其实就是一个“向上映射“的过程,既:子类类型的指针赋值给父类类型的指针;我们可以在此定义一个全局的过程来验证我们上边的话:
Procedure Test(Const pA : TA);
begin
pa.A;
{请将您的注意力放在此处,我们说了,TA是一个抽像类,它所提供的方法都是虚拟的,只有被覆盖了才可以进行应用,那么此处的处理是否是正确的呢?答案是肯定的,于是,多态在此处便提体的淋漓尽止,同时我也给出了调用的过程}
end;
procedure Tform.ButtonClick(Sender: TObject);
var
vTC : TC;
begin
vTC := TC.Create('多态实例_vTC');
Test(vTC);
vTC.Free;
end;
{此时你是否可以看明白了这种“向上映射“的机制呢?vTC做为一个子类TC的引用值(对象指针),完全可以赋值给其基类TA,于是,此处就发生了子类类型的指针赋值给父类类型的指针,于是多态就产生了!!!}
此处再一次的对晚期绑定或是动态进行说明:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。
我想,此时,你应该对多态很明白了吧,那么我们讲了多态是OOP的一个重中之重,那么它倒底有什么作用?它给我们的项目将会带来什么效率上的飞跃呢?仅仅是为了完成某种实现方法吗?仅仅是作为一个概念而存在吗?这些都是我们在理解了多态之后必须去思考的东西。那么下边就多态将会如何的影响我们的效率进行阐述。我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!
如果还没有很明白多态,不妨参考下边的例子。
在这个问题上可以这样说:大炮可以打鸟,火枪也可以打鸟,你用那个?我不管是大炮还是火枪,我只知道他们都可以打鸟!向恐龙一样大的鸟我用大炮(就是一个例子,哪有那么大的鸟啊?打什么鸟对于长官来说就是一句话:打死它们,用什么?他不管),小鸟我用火枪。敌人来了我还可以打敌人,这就是动态的最终解释和一种接口重用!
有很多地方我是应用前辈的话。
有很多地方我是应用前辈的话。
}
在对OOP的总结部分,我想说一说我对现在的程序员的一些看法,或就我而言对于自己的OOP思想的渗透进行检讨,我曾一度的迷惑,的确,我一直认为Delphi的确是一个优秀的开发工具,高效、快速、易用……,然而在相当长的一段时间内,我不知道我作项目、写代码的时候是否有一些OOP渗透在里边?有种感觉,我好像只会将那些控件脱来应用,编码也仅仅是对其进行编码,可视化的界面设计,丰富多样的可用组件让我感觉很不安,我不知道自己是否只是如作图一样,从一个地方拉来一个图,然后只会对这个图进行修饰,虽然这让我对VCL组件有了很好的应用,可从来都没有过喜悦,一直都很郁闷,难道VCL只是给了我们这些吗?于是我不再疯狂的购买VCL组件使用的书。只有当自己接触了接口编程、组件技术时,我才发现我以前在很长一段时间里,甚至是贯穿于我的整个编程生涯到那时,只将注意力集中在Delphi提供的现有的VCL组件的使用上,而忽视去思考面向对象的思想对于Delphi的整个组件构架体系所蕴含的意义。我也才深深的明白了为什么曾有前辈在说“Delphi在毒害程序员”;在此,衷心的希望各位不要停留在VCL组件的使用上,而应该用面向对象的思想去理解Delphi的整个组件构架体系所蕴涵的意义。
在理解了OOP之后,我们对于组件所要必备的另一个知识点:接口 进行阐述,也许作为刚刚接触接口的您来说,这有些枯燥,但是必须给您说明的是,此处所说的接口将会百分百的引用到以后的组件技术或是组件对象中,因为它们最终就是对接口的实现、封装!
请关注下一篇文章。