Delphi面向对象学习随笔六:接口 delphi基础

   2023-02-09 学习力0
核心提示:作者:巴哈姆特(转载请注明出处并保持完整) 在对象化中,类的继承是一个非常强大的机制;而更加强大的继承机制应该是来自从一个接口的继承。    本篇我们将讨论接口的特点。    首先,接口的定义方式与类相似。不同的是:类代表了一种实体,而接口代

作者:巴哈姆特
(转载请注明出处并保持完整)
在对象化中,类的继承是一个非常强大的机制;而更加强大的继承机制应该是来自从一个接口的继承。
    本篇我们将讨论接口的特点。
    首先,接口的定义方式与类相似。不同的是:类代表了一种实体,而接口代表了一批操作规范。还有,接口中所有的数据成员都是public访问限制,也就是说,你不能为接口中的数据成员指定其为私有或其他的域成员。另外,接口中的方法只能有声明而不能有实现,因此它看上去更像是一个没有构造和析构方法的纯虚类。
    我看的很多资料中,凡是在介绍接口的时候都会提到“多重继承”,仿佛接口的存在只是为了弥补Object Pascal不支持多重继承而设计的(至少给我的第一印象就是这样),其实接口是非常强大的,也是对象化编程中不可或缺的一个重要组成部分。
    接口之所以强大在于:接口只需要告诉用户方法的名称是什么,有什么参数;而它并不需要理会方法是怎么实现的。例如电脑的构造和工作方式对于一般用户并不重要,因为一般用户更关心的是如何去使用他。所以电脑的接口——鼠标、键盘、显示器等才是用户最关心的地方。那么这就为我们实现对象化最核心的理念——“分离”提供了相当大的便捷。

    首先我们来看看接口的定义方式:下面是Delphi中System.pas里IInterface接口的声名方式

type
  IInterface = interface
    ['']
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

 

我们可以看到,和类的基本声明差不多,只是由关键字class改成了interface。
    大家也许会注意到在紧跟在声明后的[''],这是什么呢?这是其实是接口的唯一标识,也就是我们说的TGUID;当把接口注册给系统后,我们可以通过注册表检索到00000000-0000-0000-C000-000000000046这样的键值。那么这就意味着,我们只需要知道一个TGUID的值就可以方便的访问这个接口。
    当然,你可以在接口中定义其他的方法,但是Delphi中是不允许给接口添加变量成员的,因为接口是不允许有实现部分的。

    同样,接口也可以继承、封装以及方法的覆盖。继承接口同类继承类似:

type
  INewInterface = interface(IInterface)
    // 定义一个新的接口INewInterface,并告诉编译器它是继承自IInterface接口
    ....
  end;

 

这里要注意一点,就是我们说过,接口的TGUID是每一个接口的唯一标识,那么也就是说,TGUID是不能重复的。你不能简单的从别处抄袭过来,那样是错误的。如果你需要一个TGUID,你可以在Delphi的代码编辑框中同时按下CTRL+SHIFT+G来获得一个新的TGUID值,这个值是Delphi为你自动生成的。

    接口的继承大致就是这样,和TObject是所有类的根类一样,在Delphi中IInterface是所有接口的根类,那么类似于下面的继承:

type
  INewInterface = interface
    ...
  end;

 

这里要注意一点,就是我们说过,接口的TGUID是每一个接口的唯一标识,那么也就是说,TGUID是不能重复的。你不能简单的从别处抄袭过来,那样是错误的。如果你需要一个TGUID,你可以在Delphi的代码编辑框中同时按下CTRL+SHIFT+G来获得一个新的TGUID值,这个值是Delphi为你自动生成的。

    接口的继承大致就是这样,和TObject是所有类的根类一样,在Delphi中IInterface是所有接口的根类,那么类似于下面的继承: 

其实也是定义了一个继承自IInterface的新接口INewInterface(和类名前加一个大写字母T一样,我们习惯于在接口名前加一个大写字母I,当然这只是一个命名约定)

    我们说了,接口只能有声明,不能有实现。那么怎么让接口为我们工作呢?
    其实,接口的实现是需要借助于类来完成的(当然这看上去和C++中的多重继承的写法差不多)注意,既然接口是需要借助类来实现的,那么也就是说用来实现接口的类,必须实现接口中所有已定义的方法:

type
  TNewInterfaceClass = class(TInterfacedObject, INewInterface)
    // TInterfacedObject为类名,INewInterface为我们上面定义的接口名
    ...
  end;

 

一般,我们用来实现接口的基类不会选TObject而会选TInterfacedObject,理由是TInterfacedObject类已经帮我们实现了IInterface接口中的方法,我们只需要实现我们自己接口中新的方法就可以了。

    既然我们已经通过类实现了接口中的方法,那么我们就可以使用这个接口来为我们服务了,实例化接口也非常简单:

var
  NewFace: INewInterface;
begin
  NewFace:= TNewInterfaceClass.Create(); // 创建接口
  NewFace.xxx; // 调用接口中的方法
  NewFace:= nil; // 释放
end;

 

有朋友可能会奇怪,接口的释放为什么只是直接赋为nil?我们前面说过了:接口即没有构造方法,也没有析构方法。既然没有析构方法,那么就意味着我们不能用释放类的方式来释放接口。
    那么直接把接口对象指空会造成内存泄露吗?
    答案是否定的,因为接口提供了一个引用记数的机制:当某接口实例(也就是实现了这个接口的对象)被引用,比如被赋值给一个接口变量时,该接口实例的AddRef方法会被编译器自动调用,引用计数将增加一。引用取消时编译器则会调用_Release方法将引用计数减一。引用计数减到零时,表示已无其他接口变量引用此接口实例,此时编译器会自动释放它。

    要注意的是,接口对象与类对象是不能混用的。当然像我们上面的例子里,NewFace也只能调用INewInterface接口中所定义过的方法,而不能调用类中定义的不存在于接口中的方法。

    当然,一个类可以同时实现多个接口,多个接口彼此用逗号隔开。如:

type
  TNewInterfaceClass = class(TInterfacedObject, IInterface1, IInterface2)
  ...
  end;

 

同样,实现多个接口的类必须依次实现每个接口中定义的方法。
    那么,这时出现了一个问题——就是当两个接口中有同名方法怎么办?好办,为他们取别名:

type
  IInterface1 = interface(IInterface)
    // 接口1
    fucntion Func(): Boolean;
  end;

  IInterface2 = interface(IInterface)
    // 接口2
    function Func(): Boolean;
  end;

  TClasses = class(TInterfacedObject, IInterface1, IInterface2)
  public
    function IInterface1.Func: Func1;
    function IInterface2.Func: Func2;
      { 为同名方法起别名 }
    function Func1: Boolean;
    function Func2: Boolean;
      { 声明方法 }
  end;

 

Delphi中还可以使用imploements指示符用于委托另一个类或接口来实现接口的某个方法,有时这个方法又被称为委托实现,关于implements的用法如下:

type
  TInterClass = class(TInterfacedObject, IInterface1)
    ...
    function GetClasses: TClasses;
    property Face: TClasses read GetClasses implements IInterface1;
    ...
  end;

 

上面的代码中,implements指示字会要求编译器在Face属性中寻找实现IInterface1接口的方法,属性的类型必须是一个类或一个接口。implements可以指定多个接口,彼此用逗号分隔。

    implements指示字的好处是:
    一、他允许以无冲突的方式进行接口聚合。(聚合是COM中的概念)
    二、他能够延后占用实现接口所需要的资源,直到确实需要资源。

    下面是关于委托的一个详细例子(该例在DELPHI7 + WIN2000 SP4 中调试通过):

  INewInterface = interface(IInterface)
    // 定义接口
    function SayHello: string; stdcall;
      // 接口方法
  end;

  TNewClass = class(TInterfacedObject, INewInterface)
  public
    function SayHello: string; stdcall;
      // 第一个类实现接口中的方法
  end;

  TNewClass1 = class(TInterfacedObject, INewInterface)
  private
    FNewClass: INewInterface;
  public
      // 注意,在这个类中并没有实现接口中的SyaHello方法
    constructor Create;
    destructor Destroy; override;
    property NewClass: INewInterface read FNewClass implements INewInterface;
      // 接口对象委托 如果是类对象委托应该是
    // property NewClass: TNewClass read FNewClass implements INewInterface;
  end;

implementation


function TNewClass.SayHello: string;
begin
  Result:= ClassName;
  ShowMessage(Result);
end;


constructor TNewClass1.Create;
begin
  inherited Create();
  FNewClass:= TNewClass.Create;
    // 在构造方法中创建接口
end;

destructor TNewClass1.Destroy;
begin
  FNewClass:= nil;
    // 在析构方法中释放接口
  inherited Destroy();
end;

 

调用的例子:

var
  NewInterface: INewInterface;
begin
  NewInterface:= TNewClass1.Create;
  NewInterface.SayHello;
  NewInterface:= nil;
end;

 

题外话:如果你还没有接触过COM/COM+的话,也许你会认为接口十分麻烦(PS: 当年我刚学的时候真想一脚把发明接口机制的人踹死),但是接口经过COM的封装后,将变的非常的有意义,呵呵!

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Delphi中的消息处理机制 delphi 方法
    每一个VCL都有一内在的消息处理机制,其基本点就是构件类接收到某些消息并把它们发送给适当的处理方法,如果没有特定的处理方法,则调用缺省的消息处理句柄。    其中mainwndproc是定义在Twincontrol类中的一个静态方法,不能被重载(Override)。它不直接处
    02-09
  • Delphi XE6 通过JavaScript API调用百度地图
    Delphi XE6 通过JavaScript API调用百度地图
    参考昨天的内容,有朋友还是问如何调用百度地图,也是,谁让咱都在国内呢,没办法,你懂的。 首先去申请个Key,然后看一下百度JavaScript的第一个例子:http://developer.baidu.com/map/jsdemo.htm下一步,就是把例子中的代码,移动TWebBrower中。 unit Unit
    02-09
  • Delphi编译/链接过程 delphi编程案例
    Delphi编译/链接过程 delphi编程案例
    下面展示了Delphi是怎样编译源文件,并且把它们链接起来,最终形成可执行文件。当Delphi编译项目(Project)时,将编译项目源文件、窗体单元和其他相关单元,在这个过程中将会发生好几件事情:首先,Object Pascal编译器把项目单元编译为二进制对象文件,然后
    02-09
  • Delphi CompilerVersion Constant / Compiler C
    http://delphi.wikia.com/wiki/CompilerVersion_Constant The CompilerVersion constant identifies the internal version number of the Delphi compiler.It is defined in the System unit and may be referenced either in code just as any other consta
    02-09
  • Delphi revelations #1 – kbmMW Smart client
    Delphi 启示 #1 – kbmMW Smart client on NextGen (Android) – 作用域问题以更高级的方式使用kbmMW smart client,在Android设备上,我遇到了问题。通过继承TInvokeableVariantType,kbmMW smart client可以使用Delphi支持的特殊类型的自定义Variant,从而可
    02-09
  • Delphi 调用DLL外部函数时的指针参数
    某项目需要调用设备厂家提供的DLL的函数,厂家给了一个VB的例子,有个参数是ByRef pBuffer As Single。于是在Delphi中用buffer:array of single代替:function func(buffer:array of single;count:integer):integer;far;stdcall;external 'func.dll';调用后bu
    02-09
  • 《zw版·Halcon-delphi系列原创教程》 Halcon分
    《zw版·Halcon-delphi系列原创教程》 Halcon分类函数012,polygon,多边形为方便阅读,在不影响说明的前提下,笔者对函数进行了简化::: 用符号“**”,替换:“procedure”:: 用大写字母“X”,替换:“IHUntypedObjectX”:: 省略了字符:“const”、“OleVa
    02-09
  • 最简单的delphi启动画面(转)
    首先做一窗体,然后将BorderStyle的属性设为bsnone,放image控件,align设为alclient 然后将主程序的修改为 uses Windows, Forms, Unit1 in 'Unit1.pas' {Form1}, Unit2 in 'Unit2.pas' {Form2}; {$ R *.res} begin Application.Initialize; Form2:=TForm2.Cre
    02-09
  • Delphi备忘三:TCollection的使用,用Stream保
     代码unit ufrmGetFunctionDefine;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,TypInfo,  Dialogs,ufrmStockBaseCalc, StdCtrls, ComCtrls,uQEFuncManager,uWnDataSet,uDataService;type  T
    02-09
  • Delphi Dcp 和BPL的解释
    dcp = delphi compiled package,是 package 编译时跟 bpl 一起产生出来的,记录着 package 中公开的 class、procedure、function、variable、const.... 等等的名称和相对位址。package英文翻译过来就是“包”。如果 某个控件包 A 引用了 控件包 B,当 控件包
    02-09
点击排行