开发者

Delphi - Proxy Design Pattern - interface problem

Hi I am trying to do design patterns in Delphi and, since I couldn't find a reference material that I like in Delphi, I am converting the patterns I have in the O’Reilly C# 3.0 Design Patterns book. But this is not the problem. I have created the Proxy pattern from this book but there are some concepts of Delphi interfaces, constructors and destructor and general object lifetime and behavior that I apparently don't understand. First I will post my code:

unit Unit2;  

interface  

uses
  SysUtils;

type
  ISubject = interface
  ['{78E26A3C-A657-4327-93CB-F3EB175AF85A}']
  function Request(): string;
end;

  TSubject = class
  public
    function Request(): string;
    constructor Create();
  end;

  TProxy = class (TInterfacedObject, ISubject)
  private
    FSubject: TSubject;
  public
    function Request(): String;
    destructor Destroy(); override;
  end;

  TProtectionProxy = class (TInterfacedObject, ISubject)
  private
    FSubject: TSubject;
    FPassword: String;
  public
    constructor Create();
    destructor Destroy(); override;
    function Authenticate(supplied: String): String;
    function Request(): String;
  end;

implementation

{ TSubjectAccessor.TProxy }

destructor TProxy.Destroy;
begin
  if Assigned(Self.FSubject) then
    FreeAndNil(Self.FSubject);
  inherited;
end;

function TProxy.Request: String;
begin
  if not Assigned(Self.FSubject) then begin
    WriteLn('Subject Inactive');
    Self.FSubject := TSubject.Create();
  end;
  WriteLn('Subject active');
  Result := 'Proxy: Call to ' + Self.FSubject.Request();
end;

{ TSubject }

constructor TSubject.Create;
begin
  inherited;
end;

function TSubject.Request: string;
begin
  Result := 'Subject Request Choose left door' + #10;
end;

{ TProtectionProxy }

function TProtectionProxy.Authenticate(supplied: String): String;
begin
  if (supplied <> Self.FPassword) then begin
    Result := 'Protection proxy: No Access!';
  end else begin
    Self.FSubject := TSubject.Create();
    Result := 'Protection Proxy: Authenticated';
  end;
end;

constructor TProtectionProxy.Create;
begin
  Self.FPassword := 'Abracadabra';
end;

destructor TProtectionProxy.Destroy;
begin
  if Assigned(Self.FSubject) then
    FreeAndNil(Self.FSubject);
  inherited;
end;

function TProtectionProxy.Request: String;
begin
  if not Assigned(Self.FSubject) then begin
    Result := 'Protection Proxy: Authenticate first!';
  end else begin
    Result := 'Protection Proxy: Call to ' + Self.FSubject.Request();
  end;
end;

end.

These are the interfaces and classes used in the pattern. Next, is the code that uses these types:

program Structural.Proxy.Pattern;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Unit2 in 'Unit2.pas';

var
  subject: ISubject;

begin
  ReportMemoryLeaksOnShutdown := DebugHook <> 0;

  try
    WriteLn('Proxy Pattern' +  #10);

    try
      subject := TProxy.Create();
      WriteLn(subject.Request());
      WriteLn(subject.Request());

      subject := TProtectionProxy.Create();
      WriteLn(subject.Request());
      WriteLn(TProtectionProxy(subject).Authenticate('Secret'));
      WriteLn(TProtectionProxy(subject).Authenticate('Abracadabra'));
      WriteLn(subject.Request());

      ReadLn;      
    finally

    end;

  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

Is it legal to just assign a new object instance against an interface variable? I see in debugging that the constructor for TProtectionProxy is executed first and then a destructor for TProxy. After the TProtectionProxy is created, Authenticate('Abracadabra') should be validated in logic but in debugger the FPassword is empty while it was assigned in the constructor? This one is very puzzling. B开发者_运维技巧ut when I close the application, in the destructor, the password is present? TProtectionProxy(subject) is ok but I read that is not recommended but (subject as TProtectionProxy) was not compiling for some reason (Operator not applicable...)? I have added destructors because of the FSubject field. Is that ok? Can a field variable be initiated on the same line where it is declared or I need to initiate in the constructor like in TProtectionProxy?

I know it is a lot I am asking here but I don't know anyone personally who knows Delphi OOP so well that I can ask.

Thank you.


This is the new version that works well for me. Thank you for all your help.

unit Unit2;

interface

uses
  SysUtils;

type
  ISubject = interface
  ['{78E26A3C-A657-4327-93CB-F3EB175AF85A}']
    function Request(): string;
  end;

  IProtected = interface
  ['{928BA576-0D8D-47FE-9301-DA3D8F9639AF}']
    function Authenticate(supplied: string): String;
  end;

  TSubject = class
  public
    function Request(): string;
  end;

  TProxy = class (TInterfacedObject, ISubject)
  private
    FSubject: TSubject;
  public
    function Request(): String;
    destructor Destroy(); override;
  end;

  TProtectionProxy = class (TInterfacedObject, ISubject, IProtected)
  private
    FSubject: TSubject;
    const FPassword: String =  'Abracadabra';
  public
    destructor Destroy(); override;
    function Authenticate(supplied: String): String;
    function Request(): String;
  end;

implementation

{ TSubjectAccessor.TProxy }

destructor TProxy.Destroy;
begin
  if Assigned(FSubject) then
    FreeAndNil(FSubject);
  inherited;
end;

function TProxy.Request: String;
begin
  if not Assigned(FSubject) then begin
    WriteLn('Subject Inactive');
    FSubject := TSubject.Create();
  end;
  WriteLn('Subject active');
  Result := 'Proxy: Call to ' + FSubject.Request();
end;

{ TSubject }

function TSubject.Request: string;
begin
  Result := 'Subject Request Choose left door' + #10;
end;

{ TProtectionProxy }

function TProtectionProxy.Authenticate(supplied: String): String;
begin
  if (supplied <> FPassword) then begin
    Result := 'Protection proxy: No Access!';
  end else begin
    FSubject := TSubject.Create();
    Result := 'Protection Proxy: Authenticated';
  end;
end;

destructor TProtectionProxy.Destroy;
begin
  if Assigned(FSubject) then
    FreeAndNil(FSubject);
  inherited;
end;

function TProtectionProxy.Request: String;
begin
  if not Assigned(FSubject) then begin
    Result := 'Protection Proxy: Authenticate first!';
  end else begin
    Result := 'Protection Proxy: Call to ' + FSubject.Request();
  end;
end;

end.

and the program code:

program Structural.Proxy.Pattern;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Unit2 in 'Unit2.pas';

var
  subject: ISubject;
  protect: IProtected;

begin
  ReportMemoryLeaksOnShutdown := DebugHook <> 0;

  try
    WriteLn('Proxy Pattern' +  #10);

    try
      subject := TProxy.Create();
      WriteLn(subject.Request());
      WriteLn(subject.Request());

      subject := nil;
      subject := TProtectionProxy.Create();
      WriteLn(subject.Request());
      if Supports(subject, IProtected, protect) then begin
        WriteLn(protect.Authenticate('Secret'));
        WriteLn(protect.Authenticate('Abracadabra'));
      end;
      WriteLn(subject.Request());
      ReadLn;      
    finally

    end;

  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

I have removed all the constructors cause now they really don't do anything. And the default parametherless constructors are inherited from TInrefacedObject, correct? I have left Self, I would like to hear why this shouldn't be used?

thank you

I have the full pattern implementation on http://delphipatterns.blog.com/2011/02/22/proxy-2/


You are not saying what version of Delphi you are using. The code you have given is only valid in Delphi XE and produces the following (correct) output there:

Proxy Pattern

Subject Inactive
Subject active
Proxy: Call to Subject Request Choose left door

Subject active
Proxy: Call to Subject Request Choose left door

Protection Proxy: Authenticate first!
Protection proxy: No Access!
Protection Proxy: Authenticated
Protection Proxy: Call to Subject Request Choose left door

If you look at the generated machine code:

Project2.dpr.25: WriteLn(TProtectionProxy(subject).Authenticate('Secret'));
004122C2 A1788E4100       mov eax,[$00418e78]
004122C7 8B154CF84000     mov edx,[$0040f84c]
004122CD E8E22BFFFF       call @SafeIntfAsClass
004122D2 8D4DE0           lea ecx,[ebp-$20]
004122D5 BA38244100       mov edx,$00412438
004122DA E875D9FFFF       call TProtectionProxy.Authenticate
004122DF 8B55E0           mov edx,[ebp-$20]
004122E2 A1EC3C4100       mov eax,[$00413cec]
004122E7 E8BC24FFFF       call @Write0UString
004122EC E82F25FFFF       call @WriteLn
004122F1 E82A1CFFFF       call @_IOTest

You can see how the compiler first generates a call to SafeIntfAsClass which is used to get from an ISubject pointer to a pointer for the object that is implementing ISubject. Then TProtectionProxy.Authenticate is being called with this (correct) Self pointer.

If you try to run the same code with older versions of Delphi, this will fail:

var
  subject: ISubject;
begin
...
      subject := TProtectionProxy.Create();
      WriteLn(subject.Request());
      WriteLn(TProtectionProxy(subject).Authenticate('Secret'));

Older versions of Delphi did not support safely casting from an interface back to an object. What happens then is that the compiler simply takes the value of the subject variable, and calls TProtectionProxy.Authenticate with it.

The call itself succeeds because TProtectionProxy.Authenticate is a simple static method, not a virtual method, so the compiler just generates a call to an absolute address for it. But inside TProtectionProxy.Authenticate, Self is then wrong. Because the subject pointer is different from the object pointer for the TProtectionProxy that's implementing ISubject.

The correct solution for older delphi versions is to introduce an additional interface:

type
  IProtection = interface
    ['{ACA182BF-7675-4346-BDE4-9D47CA4ADBCA}']
    function Authenticate(supplied: String): String;
  end;
...
  TProtectionProxy = class (TInterfacedObject, ISubject, IProtection)
...

var
  subject: ISubject;
  protection: IProtection;
...
      subject := TProtectionProxy.Create();
      WriteLn(subject.Request());
      if Supports(subject, IProtection, protection) then begin
        WriteLn(protection.Authenticate('Secret'));
        WriteLn(protection.Authenticate('Abracadabra'));
      end else
        WriteLn('IProtection not supported!');
      WriteLn(subject.Request());

Generally speaking, you should never mix object and interface based access. Once you got an interface reference to an object, you shouldn't keep any object references to it (because the object will get automatically freed whenever the last interface reference goes out of scope somewhere). And even though Delphi XE allows you to correctly cast back from an interface to an object, that is something you should use very very carefully.


Is it legal to just assign a new object instance against an interface variable?

  • Yes. More than that, it is the right way to use interfaces in Delphi.

I see in debugging that the constructor for TProtectionProxy is executed first and then a destructor for TProxy.

  • Does it make any change for you? That is implementation details.

If you want to destroy TProxy object first assign subject to nil:

  subject := TProxy.Create();
  WriteLn(subject.Request());
  WriteLn(subject.Request());

  subject := nil;
  subject := TProtectionProxy.Create();
  ..

After the TProtectionProxy is created, Authenticate('Abracadabra') should be validated in logic but in debugger the FPassword is empty while it was assigned in the constructor? This one is very puzzling.

  • I don't see it. FPassword is assigned as it should be.

But when I close the application, in the destructor, the password is present?

  • that is because subject is global variable. You can assign it to nil to force the object destruction manually before calling readln:

    Subject:= nil; Readln;

TProtectionProxy(subject) is ok but I read that is not recommended but (subject as TProtectionProxy) was not compiling for some reason (Operator not applicable...)?

  • I don't understand what are you trying to do. Both TProtectionProxy(subject) and (subject as TProtectionProxy) code does not seem sound.

I have added destructors because of the FSubject field. Is that ok?

  • Yes, you should destroy FSubject object instance in the destructors.

Can a field variable be initiated on the same line where it is declared or I need to initiate in the constructor like in TProtectionProxy?

  • No, you should initiate FPassword in the constructor as you did.

If you are not going to change FPassword you can declare it as constant:

  TProtectionProxy = class (TInterfacedObject, ISubject)
  private
    FSubject: TSubject;
    const FPassword: String = 'Abracadabra';
  public
    constructor Create();
    destructor Destroy(); override;
    function Authenticate(supplied: String): String;
    function Request(): String;
  end;

And don't use Self - there is no need for it in your code.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜