Ответ 1
Прежде всего, вы не должны использовать локатор сервисов для замены вызовов ctor. Это только ухудшает ситуацию. Я знаю, что люди думают, что они умны, но на самом деле вы заменяете одну простую зависимость от другого класса с зависимостью от какого-то глобального состояния, а также за то, что какой-то другой код (контроль потребления классов) ставит зависимость в контейнер. Это не приводит к упрощению, но сложнее поддерживать код.
Плюс все другие причины, почему вы должны держаться подальше от него. Локатор службы может иметь ограниченное использование в устаревшем приложении, чтобы ввести корневой состав в середине приложения, чтобы запустить DI с этой точки, но не так, как вы показываете.
Если родитель нуждается в потомке, просто введите его. Теперь проблема в том, что если вы хотите создать родителя, вам сначала нужен ребенок, но ребенку нужен родитель. Как это сделать? Существует два решения. Однако один из них не совместим с чистым DI.
Сначала я показываю способ использования factory, предоставляемого контейнером (требуется последняя версия ветки на момент публикации):
unit ParentChildRelationShip.Types;
interface
uses
SysUtils,
Spring,
Spring.Container.Common;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
property Parent: IParentObject read GetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
FChild: IChildObject;
function GetChild: IChildObject;
public
constructor Create(const childFactory: IFactory<IParentObject, IChildObject>);
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: WeakReference<IParentObject>;
function GetParent: IParentObject;
public
constructor Create(const AParent: IParentObject);
end;
implementation
{ TParentObject }
constructor TParentObject.Create;
begin
FChild := childFactory(Self);
end;
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
end.
program ParentChildRelation;
{$APPTYPE CONSOLE}
uses
SysUtils,
Spring.Container,
Spring.Container.Common,
ParentChildRelationShip.Types in 'ParentChildRelationShip.Types.pas';
procedure Main;
var
parent: IParentObject;
child: IChildObject;
begin
GlobalContainer.RegisterType<IParentObject,TParentObject>;
GlobalContainer.RegisterType<IChildObject,TChildObject>;
GlobalContainer.RegisterFactory<IFactory<IParentObject,IChildObject>>(TParamResolution.ByValue);
GlobalContainer.Build;
parent := GlobalContainer.Resolve<IParentObject>;
child := parent.Child;
Assert(parent = child.Parent);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.Message);
end;
ReportMemoryLeaksOnShutdown := True;
end.
Если вы не хотите использовать контейнер factory, вы явно зарегистрируете его самостоятельно. Затем вызов RegisterFactory заменяется следующим:
GlobalContainer.RegisterInstance<TFunc<IParentObject,IChildObject>>(
function(parent: IParentObject): IChildObject
begin
Result := GlobalContainer.Resolve<IChildObject>([TValue.From(parent)]);
end);
И параметр конструктора можно изменить на TFunc<...>
, так как для этого метода не требуется RTTI (поэтому вам понадобился IFactory<...>
в другом случае).
Вторая версия использует инъекцию поля и, таким образом, является чистой несовместимой с DI - будьте осторожны, записывая такой код, поскольку он не работает без использования контейнера или RTTI - например, если вы хотите протестировать эти классы, это может быть трудно составить их без контейнер. Важная часть здесь - PerResolve, которая сообщает контейнеру повторно использовать один раз разрешенный экземпляр, когда требуется другая зависимость, которую он может удовлетворить.
unit ParentChildRelationShip.Types;
interface
uses
SysUtils,
Spring;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
property Parent: IParentObject read GetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
[Inject]
FChild: IChildObject;
function GetChild: IChildObject;
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: WeakReference<IParentObject>;
function GetParent: IParentObject;
public
constructor Create(const AParent: IParentObject);
end;
implementation
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
end.
program ParentChildRelation;
{$APPTYPE CONSOLE}
uses
SysUtils,
Spring.Container,
Spring.Container.Common,
ParentChildRelationShip.Types in 'ParentChildRelationShip.Types.pas';
procedure Main;
var
parent: IParentObject;
child: IChildObject;
begin
GlobalContainer.RegisterType<IParentObject,TParentObject>.PerResolve;
GlobalContainer.RegisterType<IChildObject,TChildObject>;
GlobalContainer.Build;
parent := GlobalContainer.Resolve<IParentObject>;
child := parent.Child;
Assert(parent = child.Parent);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.Message);
end;
ReportMemoryLeaksOnShutdown := True;
end.
Кстати. Следите за ссылками между родителями и дочерними элементами при использовании интерфейсов. Если они ссылаются друг на друга, вы получите утечку памяти. Вы можете решить эту проблему, используя слабую ссылку с одной стороны (обычно родительскую ссылку в дочернем элементе).