我试图找出对某些遗留代码使用依赖注入的最佳方法,这将需要很长时间才能重构并且必须逐步完成。大多数旧班级使用"父母"用于确定各种事物的属性和父属性通常通过构造函数参数传递,如下所示:
constructor TParentObject.Create;
begin
FChildObject := TChildObject.Create(Self);
end;
constructor TChildObject.Create(AParent: TParentObject)
begin
FParent := AParent;
end;
这是我们遗留代码库的典型代表。但是,当移动到接口和构造函数注入时,Spring4D框架在创建Child对象时不知道Parent。所以它只会得到一个新的父母而不是现有的父母。当然我可以创建一个属性getter / setter,但这表示一个"可选"该类的财产,实际上是一个强制性财产。有关更多说明,请参阅以下代码:
unit uInterfaces;
interface
uses
Spring.Collections;
type
IChildObject = interface;
IParentObject = interface
['{8EA8F9A2-E627-4546-8008-0A77DA2B16F1}']
function GetSomethingRequiredByChild: string;
procedure SetSomethingRequiredByChild(const Value: string);
property SomethingRequiredByChild: string read GetSomethingRequiredByChild write SetSomethingRequiredByChild;
function GetChild: IChildObject;
property Child: IChildObject read GetChild;
end;
// This introduces a property getter/setter
// However it also implies that Parent can be NIL which it cannot
IChildObject = interface
['{ECCA09A6-4A52-4BE4-A72E-2801160A9086}']
function GetParent: IParentObject;
procedure SetParent(const Value: IParentObject);
property Parent: IParentObject read GetParent write SetParent;
end;
TParentObject = class(TInterfacedObject, IParentObject)
private
FChild: IChildObject;
FSomethingRequiredByChild: string;
function GetChild: IChildObject;
function GetSomethingRequiredByChild: string;
procedure SetSomethingRequiredByChild(const Value: string);
public
constructor Create;
end;
TChildObject = class(TInterfacedObject, IChildObject)
private
FParent: IParentObject;
function GetParent: IParentObject;
procedure SetParent(const Value: IParentObject);
public
// This requries a Parent object, but how does the Spring4D resolve the correct parent?
constructor Create(const AParent: IParentObject);
end;
implementation
uses
Spring.Services;
{ TParentObject }
constructor TParentObject.Create;
begin
// Here is the old way...
FChild := TChildObject.Create(Self); // Old way of doing it
// This is the Service Locator way...
FChild := ServiceLocator.GetService<IChildObject>;
// I would prefer that the Parent is assigned somehow by the Service Locator
// IS THIS POSSIBLE - or am I dreaming?
FChild.Parent := Self;
end;
function TParentObject.GetChild: IChildObject;
begin
Result := FChild;
end;
function TParentObject.GetSomethingRequiredByChild: string;
begin
Result := FSomethingRequiredByChild;
end;
procedure TParentObject.SetSomethingRequiredByChild(const Value: string);
begin
FSomethingRequiredByChild := Value;
end;
{ TChildObject }
constructor TChildObject.Create(const AParent: IParentObject);
begin
FParent := AParent;
end;
function TChildObject.GetParent: IParentObject;
begin
Result := FParent;
end;
procedure TChildObject.SetParent(const Value: IParentObject);
begin
FParent := Value;
end;
end.
也许有一些方法可以使用,我不知道使用DI框架设置父对象?
我希望这个问题很清楚我想要实现的目标。我很乐意在必要时提供更多描述/代码示例。
答案 0 :(得分:15)
首先,您不应该使用服务定位器来替换ctor调用。这只是让事情变得更糟。我知道人们认为他们是聪明的,但实际上你正在取代对另一个类的一个简单依赖,依赖于一些全局状态以及要求(消费类)控制中的一些其他代码将依赖项放入容器中。这不会导致代码更容易但更难维护。
加上所有other reasons为什么你应该远离它。服务定位器在遗留应用程序中的使用可能有限,无法在应用程序中间引入组合根,以便从该点启动DI,但不是以您显示的方式启动。
如果父母需要孩子,那么就注射它。现在问题是如果你想创建一个父,你首先需要孩子,但孩子需要父。怎么实现呢?有两种解决方案。但是其中一个不是pure DI兼容的。
我首先展示使用容器提供的工厂的方式(截至发布时需要最新的开发分支版本):
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.
如果您不想使用工厂提供的容器,请自行明确注册。然后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.
顺便说一下。使用接口时,请观察父和子之间的引用。如果他们互相引用,你会得到内存泄漏。你可以通过在一侧使用弱引用来解决这个问题(通常是孩子的父引用)。