Delphi - Proxy Design Pattern - проблема интерфейса - PullRequest
5 голосов
/ 18 февраля 2011

Привет! Я пытаюсь создавать шаблоны проектирования в Delphi и, поскольку я не смог найти справочный материал, который мне нравится в Delphi, я конвертирую шаблоны, которые есть в книге O'Reilly C # 3.0 Designs.Но это не проблема.Я создал шаблон Proxy из этой книги, но есть некоторые концепции интерфейсов Delphi, конструкторов и деструкторов, а также общее время жизни и поведение объектов, которые я, по-видимому, не понимаю.Сначала я опубликую свой код:

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.

Это интерфейсы и классы, используемые в шаблоне.Далее, является ли код, который использует эти типы:

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.

Допустимо ли просто назначать новый экземпляр объекта для переменной интерфейса?В отладке я вижу, что сначала выполняется конструктор для TProtectionProxy, а затем деструктор для TProxy.После того, как TProtectionProxy создан, Authenticate ('Abracadabra') должен быть проверен в логике, но в отладчике FPassword пуст, пока он был назначен в конструкторе?Этот очень озадачивает.Но когда я закрываю приложение, в деструкторе пароль присутствует?TProtectionProxy (тема) в порядке, но я прочитал, что это не рекомендуется, но (тема как TProtectionProxy) по какой-то причине не компилировалась (оператор не применяется ...)?Я добавил деструкторы из-за поля FSubject.Это нормально?Можно ли инициировать переменную поля в той же строке, где она объявлена, или мне нужно инициировать ее в конструкторе, как в TProtectionProxy?

Я знаю, что это много, что я спрашиваю здесь, но я никого не знаю личнокто знает Delphi ООП так хорошо, что я могу спросить.

Спасибо.


Это новая версия, которая хорошо работает для меня.Спасибо за вашу помощь.

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.

и программный код:

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.

Я удалил все конструкторы, потому что теперь они действительно ничего не делают.И конструкторы по умолчанию без параметров наследуются от TInrefacedObject, правильно?Я оставил Self, я хотел бы услышать, почему это не должно использоваться?

спасибо

У меня есть полная реализация шаблона на http://delphipatterns.blog.com/2011/02/22/proxy-2/

Ответы [ 2 ]

5 голосов
/ 18 февраля 2011

Вы не говорите, какую версию Delphi вы используете.Код, который вы дали, действителен только в Delphi XE и выдает следующий (правильный) вывод:

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

Если вы посмотрите на сгенерированный машинный код:

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

Вы можетеПосмотрите, как компилятор сначала генерирует вызов SafeIntfAsClass, который используется для получения от указателя ISubject указателя для объекта, реализующего ISubject.Затем вызывается TProtectionProxy.Authenticate с этим (правильным) указателем Self.

Если вы попытаетесь запустить тот же код с более старыми версиями Delphi, произойдет сбой:

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

Старые версииDelphi не поддерживает безопасное приведение от интерфейса обратно к объекту.Затем происходит то, что компилятор просто принимает значение переменной объекта и вызывает TProtectionProxy.Authenticate вместе с ним.

Сам вызов успешно завершается, потому что TProtectionProxy.Authenticate является простым статическим методом, а не виртуальным методом, поэтомукомпилятор просто генерирует вызов для абсолютного адреса для него.Но внутри TProtectionProxy.Authenticate, Self тогда не прав.Поскольку указатель субъекта отличается от указателя объекта для TProtectionProxy, который реализует ISubject.

Правильное решение для более старых версий Delphi - это ввести дополнительный интерфейс:

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());

Вообще говоря, выникогда не следует смешивать объект и доступ на основе интерфейса.После того, как вы получили ссылку на интерфейс объекта, вы не должны сохранять никаких ссылок на него (поскольку объект будет автоматически освобожден всякий раз, когда последняя ссылка на интерфейс выходит из области видимости).И даже несмотря на то, что Delphi XE позволяет вам правильно выполнить приведение обратно с интерфейса к объекту, вы должны использовать это очень и очень осторожно.

2 голосов
/ 18 февраля 2011

Разрешено ли просто назначать новый экземпляр объекта переменной интерфейса?

  • Да. Более того, это правильный способ использования интерфейсов в Delphi.

В отладке я вижу, что сначала выполняется конструктор для TProtectionProxy, а затем деструктор для TProxy.

  • Это что-то меняет для вас? Это детали реализации.

Если вы хотите уничтожить объект TProxy, сначала присвойте субъекту nil:

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

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

После того, как TProtectionProxy создан, Authenticate ('Abracadabra') должен быть проверен в логике, но в отладчике FPassword пуст, пока он был назначен в конструкторе? Этот очень озадачивает.

  • Я этого не вижу. FPassword назначается так, как должно быть.

а когда закрываю приложение, в деструкторе пароль присутствует?

  • это потому, что субъект является глобальной переменной. Вы можете присвоить ему значение nil, чтобы принудительно уничтожить объект перед вызовом readln:

    Тема: = ноль; ReadLn;

TProtectionProxy (тема) в порядке, но я прочитал, что это не рекомендуется, но (тема как TProtectionProxy) по какой-то причине не компилировалась (оператор не применяется ...)?

  • Я не понимаю, что ты пытаешься сделать. Код TProtectionProxy (подчиненный) и (подчиненный как TProtectionProxy) не выглядит надежным.

Я добавил деструкторы из-за поля FSubject. Это нормально?

  • Да, вы должны уничтожить экземпляр объекта FSubject в деструкторах.

Может ли переменная поля инициироваться в той же строке, где она объявлена, или мне нужно инициировать ее в конструкторе, как в TProtectionProxy?

  • Нет, вы должны инициировать FPassword в конструкторе, как и вы.

Если вы не собираетесь менять FPassword, вы можете объявить его как константу:

  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;

И не используйте Self - в вашем коде нет необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...