Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

Как разрешить циклические зависимости в Делфи?

#delphi #ооп #delphi_xe5 #зависимости


Допустим у нас есть 2 класса, расположенных в разных юнитах:


TMaster - хозяин собаки, который должен о ней знать
TDog - собака, которая должна знать о своем хозяине


Unit_Master:

uses Unit_Dog;
type TMaster = class
public
  Dog: TDog;
end;


Unit_Dog:

uses Unit_Master;
type TDog = class
public
  Master: TMaster;
end;


Использование таких прямых ссылок между классами очень облегчило бы жизнь. К сожалению,
Delphi не может скомпилировать такой код из-за циклической зависимости между юнитами.

Как грамотно разрулить циклическую зависимость и скомпилировать подобный код, какие
есть способы?
    


Ответы

Ответ 1



Можно использовать интерфейсы и в классах хранить ссылки на них, а не на объекты: unit EntityInterfaces type IMaster = interface; IDog = interface; IMaster = interface ['{8417E5A8-02FE-4A83-BD0C-F69E79492796}'] function GetDog: IDog; procedure SetDog(const ADog: IDog); property Dog: IDog read GetDog write SetDog; end; IDog = interface ['{9B501AD7-BD77-46AC-BC08-545433EC5FFE}'] function GetMaster: IMaster; procedure SetMaster(const AMaster: IMaster); property Master: IMaster read GetMaster write SetMaster; end; Сами классы в этом случае объявляются так: TMaster = class(TInterfacedObject, IMaster) private [Weak] FDog: IDog; function GetDog: IDog; procedure SetDog(const ADog: IDog); end; TDog = class(TInterfacedObject, IDog) private [Weak] FMaster: IMaster; function GetMaster: IMaster; procedure SetMaster(const AMaster: IMaster); end; И работа ведется уже с интерфейсами: procedure TForm11.btn1Click(Sender: TObject); var Master: IMaster; Dog: IDog; begin Master := TMaster.Create; Dog := TDog.Create; Master.Dog := Dog; Dog.Master := Master; end; // на выходе из метода Master и Dog будут автоматически удалены, // поскольку их "циклические ссылки" друг на друга имеют атрибут Weak // без использования слабых ссылок возникла бы утечка памяти. Недостатки способа: необходимость дважды описывать методы - в интерфейсе и непосредственно в реализации. автоудаление объекта при доведении счетчика ссылок до нуля будет непривычно тем, кто не работал с интерфейсами. Для ликвидации последнего недостатка можно сделать собственную реализацию IUnknown и наследовать свои классы не от TInterfacedObject, а от TNoReferenceObject : type TNoReferenceObject = class(TObject, IInterface) { IInterface } function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end; { TNoReferenceObject } function TNoReferenceObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TNoReferenceObject._AddRef: Integer; begin Result := -1; end; function TNoReferenceObject._Release: Integer; begin Result := -1; end; Подсчет ссылок для всех наследников TNoReferenceObject не будет действовать и все созданные экземпляры нужно будет удалять вручную, как и обычные объекты: procedure TForm11.btn1Click(Sender: TObject); var Master: IMaster; Dog: IDog; begin Master := TMaster.Create; Dog := TDog.Create; try Master.Dog := Dog; Dog.Master := Master; finally TObject(Master).Free; TObject(Dog).Free; end; end;

Ответ 2



Вариант №1 Объединить оба класса в 1 файл и воспользоваться forward declaration: Unit: type TDog = class; // т.н. forward declaration TMaster = class public Dog: TDog; end; TDog = class public Master: TMaster; end; Минусы очевидны - получаем один огроменный файл с кучей кода.

Ответ 3



Вариант №2 Создать общего предка, с общими методами: Unit_Entity: type TEntity = class end; Unit_Dog: uses Unit_Entity; type TMaster = class(TEntity) public Dog: TEntity; end; Unit_Dog: uses Unit_Entity; type TDog = class(TEntity) public Master: TEntity; end; Минусы в том, что весь код специфический для каждого класса по прежнему остается в каждом классе и недоступен без дополнительных решений.

Ответ 4



Вариант №3 Использовать общего предка и приводить типы при обращении к объектам: Unit_Master: type TMaster = class public Dog: TObject; end; implementation uses Unit_Dog; .. TDog(Dog).Bark; Unit_Dog: type TDog = class public Master: TObject; end; implementation uses Unit_Master; .. TMaster(Master).Yell; Минусы в том, что при каждом обращении нам придется приводить объект к определенному классу. При большом количестве обращений, это загромождает код.

Ответ 5



Вариант №4 Использовать помощники классов. (Способ через private официально перестал работать с Delphi Seattle (но есть пути как обойти: https://stackoverflow.com/questions/37351215) и, вероятно, можно будет перейти на protected) Unit_Helper: uses Unit_Master, Unit_Dog; type TMasterHelper = class helper for TMaster private function GetDog: TDog; procedure SetDog(aObject: TDog); public property Dog: TDog read GetDog write SetDog; end; TDogHelper = class helper for TDog private function GetMaster: TMaster; procedure SetMaster(aObject: TMaster); public property Master: TMaster read GetMaster write SetMaster; end; function TMasterHelper.GetDog: TDog; begin Result := fDog; end; Unit_Master: type TMaster = class protected fDog: TObject; end; uses Unit_Helper; .. Dog.Bark; Unit_Dog: type TDog = class protected fMaster: TObject; end; uses Unit_Helper; .. Master.Yell; Минусы в том, что у класса может быть максимум 1 помощник, следовательно воспользоваться этим трюком можно только 1 раз.

Комментариев нет:

Отправить комментарий