Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. But 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/

like image 648
elector Avatar asked Feb 17 '11 21:02

elector


People also ask

What is proxy design pattern?

Proxy means ‘in place of’, representing’ or ‘in place of’ or ‘on behalf of’ are literal meanings of proxy and that directly explains Proxy Design Pattern. Proxies are also called surrogates, handles, and wrappers. They are closely related in structure, but not purpose, to Adapters and Decorators.

What is proxy microservice pattern?

Proxy microservice pattern is a variation of the aggregator model. In this model we will use proxy module instead of the aggregation module. Proxy service may call different services individually. In Proxy pattern, we can build one level of extra security by providing a dump proxy layer. This layer acts similar to the interface. Advantages

What is observation design pattern in Delphi?

Observer Design Pattern in Delphi. A common side-effect of partitioning a system into a collection of co-operating classes, is the need to maintain consistency between related objects. Delphi's events (which are actually method pointers) let you deal with this problem in a structured manner. Events let you decouple classes that need to co-operate.

What is a proxy in web development?

A proxy is simply a substitute object for the original object. It also acts as a wrapper or agent object which, is called by the client to access the original object behind the scene. A proxy is a lightweight object that implements the same interface as the original actual object as well as controls the access to the actual object.


1 Answers

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.

like image 118
Thorsten Engler Avatar answered Oct 05 '22 20:10

Thorsten Engler