Delphi面向对象学习随笔八:物理封装

作者:巴哈姆特
http://www.cnpack.org
(转载请注明出去并保持完整)
前面说过的封装其实是逻辑意义上的封装。逻辑封装是对某一特定逻辑功能模块的封装,这个特定逻辑功能块可以是一个类,当然也可以是一个包,他们都有自己的逻辑边界。另一种封装方式,我们通常叫它为物理封装:物理封装其实是具体实现代码的物理集合,他可以以bpl,dll,com+等形式体现。

逻辑封装里,对象的传递、数据共享与调用相对要简单的多,只要我们引用类所定义的单元(unit)就可以直接访问类中public和published所公布出来的属性或方法,在编译的时候,编译器会把工程内所有引用的单元全部打包到exe中。逻辑封装最终是以一个独立的物理文件存在的。虽然简单,但是无法实现物理上的切割,一旦其中某个单元或代码段发生改动,那么其他的单元或代码段也需要重新编译和连接。

而在物理封装中,对象的传递、数据共享与调用要复杂的多了,由于在编译的时候,exe和dll或bpl是两个或多个文件,所以你无法像在逻辑封装中那样简单的uses那个unit。而物理封装的好处是可以减少维护量,因为每个dll都是动态调用的,所以,我们只需要更新我们改动过的相应的dll,而其他的部分则可以不用改动。

用DLL封装对象:
    用DLL封装函数,我想几乎是所有程序员熟悉到不能再熟悉的技术,而且我们可以找到很多相关的书籍和资料。这里我们只讨论怎么用DLL来封装对象。
    用DLL封装对象有以下的好处:
      一、可以节约内存。我们可以在使用到DLL资源的时候动态装载,不用时释放。
      二、提高代码重用。DLL在封装好以后,我们可以使用任何一个支持DLL的开发工具来调用它。
      三、可以使软件拆分成若干个小块,这样可以有效的降低维护量。
    注意:如果你只为了减少软件体积而使用动态库,那么我建议你还是放弃使用动态库吧。

当然,想使用DLL封装对象也有一定的困难:
      一、调用DLL的EXE只能使用DLL中对象的动态绑定的方法。
      二、DLL中的对象只能在DLL中创建。
      三、在DLL和调用方,都需要对封装的对象和被调用的方法进行声明。

我们来看下面的例子:

首先我们声明一个类:

type

  TNewClass = class(TObject)

  public

    procedure SayHello; virtual;

      // 注意,这里不能使用静态方法,必须使用动态绑定(或者说晚绑定)技术。

      // 至于为什么——虚方法表有关,大家可以找其他资料详细研究^_^

  end;

procedure TNewClass.SayHello; // 实现部分

begin

  ShowMessage(‘Hello‘);

end;

新建一个Library项目

library dll;

function GetObj: TNewClass; stdcall;

begin // 创建对象

  Result:= TNewClass.Create;

end;

exports GetObj; // 定义输出函数

end.

下面,我们创建一个EXE工程,并且添加类的声明:

type

  TNewClass = class(TObject)

  public

    procedure SayHello; virtual; abstract;

      { 注意这里的声明方式和DLL中的不同,这里必须声明为virtual方法,还有由于此方法通过晚绑定用的是DLL中的实现,因此EXE中可不写其实现而声明成abstract方法。 }

  end;

function GetObj: TNewClass; stdcall; external ‘dll.dll‘;

之后,我们可以添加一段测试代码来测试我们是否实现了DLL对象的共用:

var

  NewClass: TNewClass;

begin

  NewClass:= GetObj;

  if not Assigned(NewClass) then

    Exit;

  try

    NewClass.SayHello;

  finally

    FreeAndNil(NewClass);

  end;

end;

我们可以看到,这的确达到了EXE与DLL之间传递对象的目的。
    但是,有点麻烦:首先,在DLL工程与EXE工程都需要有被封装对象的定义。其次,virtual和abstract必须正确使用。还有,如果一旦对象发生变化,那么两边的定义都需要修改,这样难免会出点小错。

其实,我们可以使用接口来进行对象的传递,上面的例子我们可以稍微修改一下:

首先,我们定义一个接口:

type

  INewInterface = interface(IInterface)

    procedure SayHello(); // 定义我们要的方法

  end;

另外,修改TNewClass类的声名:

type

  TNewClass = class(TInterfacedObject, INewInterface)

  public

    procedure SayHello();

  end;

实现部分无须改动。
    接着,我们修改先前的那个Library项目:

library dll;

function GetObj: INewInterface; stdcall;

begin // 创建对象

  Result:= TNewClass.Create;

end;

exports GetObj; // 定义输出函数

end.

在EXE工程中,我们直接引用接口定义的单元,并且修改输出函数的声明:

function GetObj: INewInterface; stdcall; external ‘dll.dll‘;

之后,测试代码会成这样:

var

  NewInterface: INewInterface;

begin

  NewInterface:= GetObj;

  NewInterface.SayHello;

  NewInterface:= nil;

end;

这样做的好处是,我们可以避免在多处重复说明一个要传递的对象的声明,只要我们需要的方法的声明方式不动,我们只需要改动TNewClass的实现代码,而无需改动EXE程序中的任何代码部分。
    PS:Delphi的OpenToolsAPI接口就是这个通过接口共享对象原理的很典型的应用。(这是刘啸说的。老实说,这个用法是我在写这个笔记的时候临时想到的,因为从来没有使用过未经COM封装的interface。哪里知道竟然和OpenToolAPI一样的原理,自己YY下^_^)

当然我们还可以使用COM来封装对象:
    首先,我们建立一个名为NewCom的COM模型,建立COM模型前一篇已经说过,这里不再重复。
    那么,我们的调用代码就会变成这样:

var

  NewCom: ITNewCom;

begin

  NewCom:= CoTNewCom.Create;

    // 当然,和我前一篇一样使用CreateComObject函数也是一样的

  NewCom.SayHello;

  NewCom:= nil;

end;

我们可以看到,实现代码几乎没什么改动。那么,假如我们什么时候要把SayHello的实现代码:

ShowMessage(‘Hello‘);

改成:

MessageBox(0, ‘Hello‘, ‘SayHello‘, MB_OK);

那么,我们只需要更新这个COM文件,调用它的EXE程序无须改动,这就是接口的优点。

时间: 2024-10-05 04:09:29

Delphi面向对象学习随笔八:物理封装的相关文章

Delphi面向对象学习随笔九:后记

作者:巴哈姆特http://www.cnpack.org(转载时请注明出处并保持完整) 最后一篇了,呵呵!其实通过写这几篇笔记,也发现了我自己知识欠缺的部分.当然也通过各位高手的提点,让我把以前学习过的东西来了一次“体检”.    当然,错误也有,但是改了就还是好同志嘛,呵呵^_^ 随便介绍一下Delphi中的几个比较常用的类吧: TObject:    VCL中所有类的根类,即是说:VCL中所有的类/组件/控件都是从TObject中继承而来.TObject类中定义了基本的构造方法和析构方法.

Delphi面向对象学习随笔七:COM

作者:巴哈姆特http://www.cnpack.org(转载请注明出处并保持完整) 上一篇,我们介绍了接口.如果没有接触过COM对象的话,你会觉得接口真的很麻烦,也许会有:“还不如直接定义一个类更方便”的想法.    的确,没有经过COM封装的接口确实比较麻烦.在我看来,没有经过COM封装的接口似乎没有存在的意义.那么,什么是COM对象呢?它有什么优点呢?接下来开始对COM对象进行一个简单的介绍: COM是个二进制规范,它与实现的语言无关.这样,即使COM对象由不同的编程语言创建,运行在不同的

Delphi面向对象学习随笔三:overload与override

作者:巴哈姆特(转载请注明出处并保持完整)    首先,我想单独说明一下overload,为什么呢?因为overload和对象化关联不大,所以,我感觉单独提出来说明比较好.    我们都知道,在Pascal语法规则中,同一个UNIT里是不能存在两个同名的函数的,例如: function func(): Boolean; function func(const x: Char): Boolean; 这样是会出语法错误的,原因是因为标识符规则限制.但是问题出来了,如果我们需要几个功能相似但是参数不同

Delphi面向对象学习随笔二 编写第一个类

作者:巴哈姆特(转载请注明出处并保持完整) 这回,我们讨论怎么编写我们自己的第一个类.    在编写我们自己的类之前,首先要说的是“类的继承”.    记得前几天,我在和一个朋友讨论类的特点的时候,他说:“类是可以没有构造方法的!”其实类必须有至少一个构造方法的,但是他的话也不全错,可以理解成“我们可以不实现我们自己的构造方法”.    当我们没有显式的为类编写一个构造方法的时候,那么,看上去,这个类好象是没有构造方法,但是实际上,就算你没有为这个类编写一个属于你自己的构造方法的时候,该类还是有

Delphi面向对象学习随笔五:一个真正的类

作者:巴哈姆特(转载请注明出去并保持完整)写在前面的话:    本篇笔记完全属于我的个人主观观点,如有错误请指正^_^  类的定义:    首先,我想说的是,类并不是一些变量和函数简单的“拼凑”出来的.类应该是对于一个事物的抽象描述,而不是一个动作的抽象描述.怎么讲呢?    比如说:鞋子是一个事物,我们可以把它的特点抽象出来,并用计算机语言去描述成为一个类,而鞋子又分了凉鞋.皮鞋等,那么“凉鞋”和“皮鞋”则是“鞋子”的派生类.它们看上去是非常自然的.    那么,现在我有另外一个类,“初始化数

Delphi面向对象学习(-)

Delphi面向对象学习随笔一:类与对象的关系作者:巴哈姆特http://www.cnpack.org(转载请注明出处并保持完整)工作几年了,总想做点总结,于是有了这篇东西,叫随笔吧呵    本文只是写写我对对象化的理解,主观成分很多,或许有错误,希望大家指正^_^    PS: 本文的演示代码均以Delphi 的Object Pascal语法为准. 类与对象的关系    要讨论类与对象的关系,需要先说一下什么是类,什么是对象. 类:    类.我认为是一个集合,和数学中的集合一样,是一类事物的

lua面向对象学习随笔 --类与实例

面向对象最基本的就是两点:类,对象. 但是lua是无类型的,要实现面向对象只能模拟实现. 其实他们都是表,多了个__index属性,就模拟出了实现“类和继承”的效果. 面对Lua千万别用c++的类来类比,不然会误入歧途! 它就是一个table而已下面一个典型的定义一个“类”的方式. class={} function class:new(o) local o=o or {} setmetatable(o,self) self.__index=self o:ctor() return o end

linux学习随笔八

这节主要学习用户.组.权限 权限: r,w,x 文件: r:可读,可以使用类似cat等命令查看文件内容 w: 可写,可以编辑或删除此文件 x:可执行,exacutable,可以命令提示符下当作命令提交给内核运行 目录: r:可以对此目录执行ls以列出内部的所有文件 w:可以在此目录创建文件 x:可以使用cd切换进此目录,也可以使用ls -l查看内部文件的详细信息 如: 000 ---:无权限 (二进制) 777 rwx:读写执行(777的表示是十进制) rwx:4 2 1 用户:UID  /et

面向对象学习随笔

面向对象中讲的属性与构造函数,现象与对象,继承多态,接口,抽象类都会用到类和对象.[所以类和对象的理解和运用很重要!] 类不能直接使用 对象才可以使用 class 类名                    {                                           }     类的里面应该定义什么:         a. 将这类事物拥有的共同特征定义为类的成员变量.         b. 将这类事物拥有的共同的功能定义为方法.(我们在自己写的类中 不要加static