• Why we need interfaces in Delphi


    http://sergworks.wordpress.com/2011/12/08/why-we-need-interfaces-in-delphi/

    Why we need interfaces in Delphi.

    Objects are normally accessed by an object reference.

    Interface reference is a different method to access an object’s functionality.

    A simple question – why do we need interface references at all, why can’t we use object references everywhere?

    There are several reasons to use interface references instead of object references,

    but most important of them (at least historically) is accessing an object created in a different program module.

    Let us consider a simple example – an object is created in .dll module and consumed in .exe module.

    The TMathObject class implements Square and Cube functions on the FOperandfield;

    we start with the following naive code:

    复制代码
    unit MathUnit;
     
    interface
     
    type
      TMathObject = class
      private
        FOperand: Double;
      public
        function Square: Double;
        function Cube: Double;
        property Operand: Double read FOperand write FOperand;
      end;
     
    implementation
     
    function TMathObject.Square: Double;
    begin
      Result:= Sqr(FOperand);
    end;
     
    function TMathObject.Cube: Double;
    begin
      Result:= Sqr(FOperand) * FOperand;
    end;
     
    end.
    复制代码

    We want to create and destroy TMathObject instances in dll module:

    复制代码
    library MathDll;
     
    uses
      MathUnit in 'MathUnit.pas';
     
    function CreateObject: TMathObject;
    begin
      Result:= TMathObject.Create;
    end;
     
    procedure FreeObject(Obj: TMathObject);
    begin
      Obj.Free;
    end;
     
    exports
      CreateObject, FreeObject;
     
    {$R *.res}
     
    begin
    end.
    复制代码

    and use an instance of TMathObject in exe module:

    复制代码
    program MathTest;
     
    {$APPTYPE CONSOLE}
     
    uses
      MathUnit in 'MathUnit.pas';
     
    function CreateObject: TMathObject; external 'MathDll.dll';
    procedure FreeObject(Obj: TMathObject); external 'MathDll.dll';
     
    var
      MathObj: TMathObject;
     
    begin
      MathObj:= CreateObject;
      MathObj.Operand:= 2;
      Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
      FreeObject(MathObj);
      Write('Press ''Enter'' key ... ');
      Readln;
    end.
    复制代码

    If you compile the above example you can see it works, but TMathObject implementation (MathUnit.pas) is duplicated

    in both program modules (MathTest.exe and MathDll.dll), and that is not just a waste of program memory.

    One of the main reasons to split a program into program modules is a possibility to modify the modules separately;

    for example to modify and deploy a different .dll version while keeping an .exe module intact.

    In the above example the implementation of TMathObject is a contract that both sides (exe and dll) should adhere,

    so the implementation of TMathObject can’t be changed in dll module only.

    We need a different form of contract that does not include an object’s implementation.

    A possible solution is to introduce a base class containing virtual abstract methods only:

    复制代码
    unit BaseMath;
     
    interface
     
    type
      TBaseMathObject = class
      protected
        function GetOperand: Double; virtual; abstract;
        procedure SetOperand(const Value: Double); virtual; abstract;
      public
        function Square: Double; virtual; abstract;
        function Cube: Double; virtual; abstract;
        property Operand: Double read GetOperand write SetOperand;
      end;
     
    implementation
     
    end.
    复制代码
    Note that we can’t access FOperand field directly now because it is a part of TMathObject implementation that should be hidden in .dll module,
    so we introduce getter (GetOperand) and setter (SetOperand) virtual methods.
    Now we inherit a class that implements virtual methods from TBaseMathObject.
    复制代码
    unit MathUnit;
     
    interface
     
    uses BaseMath;
     
    type
      TMathObject = class(TBaseMathObject)
      private
        FOperand: Double;
      protected
        function GetOperand: Double; override;
        procedure SetOperand(const Value: Double); override;
      public
        function Square: Double; override;
        function Cube: Double; override;
      end;
     
    implementation
     
    function TMathObject.GetOperand: Double;
    begin
      Result:= FOperand;
    end;
     
    procedure TMathObject.SetOperand(const Value: Double);
    begin
      FOperand:= Value;
    end;
     
    function TMathObject.Square: Double;
    begin
      Result:= Sqr(FOperand);
    end;
     
    function TMathObject.Cube: Double;
    begin
      Result:= Sqr(FOperand) * FOperand;
    end;
     
    end.
    复制代码

    The library module source code now is

    复制代码
    library MathDll;
     
    uses
      BaseMath in 'BaseMath.pas',
      MathUnit in 'MathUnit.pas';
     
    function CreateObject: TBaseMathObject;
    begin
      Result:= TMathObject.Create;
    end;
     
    procedure FreeObject(Obj: TBaseMathObject);
    begin
      Obj.Free;
    end;
     
    exports
      CreateObject, FreeObject;
     
    {$R *.res}
     
    begin
    end.
    复制代码
    The executable module source code is
    复制代码
    program MathTest;
     
    {$APPTYPE CONSOLE}
     
    uses
      BaseMath in 'BaseMath.pas';
     
    function CreateObject: TBaseMathObject; external 'MathDll.dll';
    procedure FreeObject(Obj: TBaseMathObject); external 'MathDll.dll';
     
    var
      MathObj: TBaseMathObject;
     
    begin
      MathObj:= CreateObject;
      MathObj.Operand:= 2;
      Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
      FreeObject(MathObj);
      Write('Press ''Enter'' key ... ');
      Readln;
    end.
    复制代码

    We can see that MathTest project does not contain MathUnit.pas unit, and is not dependent on TMathObject implementation;

    in fact MathTest project does not know that TMathObject class even exist.

    We can change TMathObjectimplementation in dll module as much as we want provided that we keepTBaseMathObject intact,

    inherit TMathObject from TBaseMathObject and override TBaseMathObject‘s virtual abstract methods.


    We implemented a general concept of interface in the form of pure abstract class.

    Pure abstract classes are a way how interfaces are implemented in C++ .

    This approach has a limited value in Delphi because Delphi does not support multiple inheritance,

    and a Delphi class can have only one contract in the form of base abstract class.

    Another problem is a limited use of ‘is’ and ‘as’ operators for an object created in a different program module:

    Starting from version 3 Delphi introduces a concept of interface that is different from a pure abstract class

    and solves the problems with object’s export by using interface references instead of object references:

    复制代码
    unit BaseMath;
     
    interface
     
    type
      IBaseMath = interface
      ['{92E9AFF4-25B7-41BD-9EB6-557D12F98BE6}']
        function GetOperand: Double;
        procedure SetOperand(const Value: Double);
        function Square: Double;
        function Cube: Double;
        property Operand: Double read GetOperand write SetOperand;
      end;
     
    implementation
     
    end.
    复制代码

    There is no need to inherit TMathObject class from a given base class now;

    we can inherit TMathObject class from any class we like.

    Since all Delphi interfaces are descendants of IUnknown (also nicknamed as IInterface in Delphi)

    we should also implement the methods of IUnknown interface in TMathObject class.

    Delphi provides a helper TInterfacedObject class that already implements the methods of IUnknown 

    and can be used as TMathObject ancestor:

    复制代码
    unit MathUnit;
     
    interface
     
    uses BaseMath;
     
    type
      TMathObject = class(TInterfacedObject, IBaseMath)
      private
        FOperand: Double;
      protected
        function GetOperand: Double;
        procedure SetOperand(const Value: Double);
      public
        function Square: Double;
        function Cube: Double;
      end;
     
    implementation
     
    function TMathObject.GetOperand: Double;
    begin
      Result:= FOperand;
    end;
     
    procedure TMathObject.SetOperand(const Value: Double);
    begin
      FOperand:= Value;
    end;
     
    function TMathObject.Square: Double;
    begin
      Result:= Sqr(FOperand);
    end;
     
    function TMathObject.Cube: Double;
    begin
      Result:= Sqr(FOperand) * FOperand;
    end;
     
    end.
    复制代码

    There is no need for FreeObject procedure now.

    The FreeObject procedure was introduced in the previous examples

    to enforce that a TMathObject instance is destroyed

    in the same program module where it was created (i.e. in .dll module).

    It is always a good rule of thumb that the one who creates an object is the one who destroys it.

    But now there is no need to enforce it – if we use interface references object instances

    are automatically destroyed in the same program module where they were created.

    复制代码
    library MathDll;
     
    uses
      BaseMath in 'BaseMath.pas',
      MathUnit in 'MathUnit.pas';
     
    function CreateObject: IBaseMath;
    begin
      Result:= TMathObject.Create;
    end;
     
    exports
      CreateObject;
     
    {$R *.res}
     
    begin
    end.
    复制代码

    In the next example a TMathObject object instance is destroyed by assigning nil value to MathObj interface reference.

    In most cases there is no need for doing it because an object is

    destroyed automatically when all interface references goes out of scope.

    In the following code the MathObj interface reference is a global variable and never goes out of scope,

    so assigning it to nil explicitly makes sense:

    复制代码
    program MathTest;
     
    {$APPTYPE CONSOLE}
     
    uses
      BaseMath in 'BaseMath.pas';
     
    function CreateObject: IBaseMath; external 'MathDll.dll';
     
    var
      MathObj: IBaseMath;
     
    begin
      MathObj:= CreateObject;
      MathObj.Operand:= 2;
      Writeln('Square = ', MathObj.Square:3:2, '; Cube = ', MathObj.Cube:3:2);
      MathObj:= nil;
      Write('Press ''Enter'' key ... ');
      Readln;
    end.
    复制代码

    The interfaced demo works fine, but when I try to load/unload the DLL dinamically,

    an access violation is raised at the end of the program in the System unit at the _IntfClear function.

    复制代码
    program MathTest;
    
    {$APPTYPE CONSOLE}
    
    uses
    Windows,
    BaseMath in ‘BaseMath.pas';
    
    //function CreateObject: IBaseMath; external ‘MathDll.dll';
    
    type
    
    TCreateObject = function: IBaseMath; stdcall;
    
    var
    
    CreateObject: TCreateObject;
    MathObj: IBaseMath;
    Dll: THandle;
    
    begin
    Dll := LoadLibrary(‘MathDll.dll’);
    CreateObject := GetProcAddress(Dll, ‘CreateObject’);
    
    MathObj:= CreateObject;
    MathObj.Operand:= 2;
    Writeln(‘Square = ‘, MathObj.Square:3:2, ‘; Cube = ‘, MathObj.Cube:3:2);
    MathObj:= nil;
    
    FreeLibrary(Dll);
    
    Write(‘Press ”Enter” key … ‘);
    Readln;
    // Access Violation is raised here
    end.
    复制代码

    Sorry. It was a calling convention problem.
    TCreateObject = function: IBaseMath;
    works fine.

    Great articles.

    Great Approach of explanation.

    I read a lot about interfaces, but this articles make you understand EXACTLY what is an Interface.

    The three examples is the best way to progressively move from one concept to the other.

    Thank you very much for your efforts.

    http://www.cnblogs.com/shangdawei/p/3983135.html
  • 相关阅读:
    Mybatis学习随笔3
    Mybatis学习随笔2
    Mybatis学习随笔
    Java校招面试-什么是线程安全/不安全
    装饰器2
    装饰器
    默认传参的陷阱
    处理日志文件
    第二天
    用户登录
  • 原文地址:https://www.cnblogs.com/findumars/p/5243919.html
Copyright © 2020-2023  润新知