Ric*_*ler 13 delphi dependency-injection spring4d
我正在试图找出对一些遗留代码使用依赖注入的最佳方法,这将需要很长时间才能重构并且必须逐步完成.大多数旧类使用"Parent"属性来确定各种事物,而父属性通常通过构造函数参数传递,如下所示:
constructor TParentObject.Create;
begin
FChildObject := TChildObject.Create(Self);
end;
constructor TChildObject.Create(AParent: TParentObject)
begin
FParent := AParent;
end;
Run Code Online (Sandbox Code Playgroud)
这是我们遗留代码库的典型代表.但是,当移动到接口和构造函数注入时,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.
Run Code Online (Sandbox Code Playgroud)
也许有一些方法可以使用,我不知道使用DI框架设置父对象?
我希望这个问题很清楚我想要实现的目标.我很乐意在必要时提供更多描述/代码示例.
Ste*_*nke 15
首先,您不应该使用服务定位器来替换ctor调用.这只是让事情变得更糟.我知道人们认为他们是聪明的,但实际上你正在取代对另一个类的一个简单的依赖,依赖于一些全局状态加上要求(消费类)控制中的一些其他代码将依赖项放入容器中.这不会导致代码更容易但更难维护.
加上你应该远离它的所有其他原因.服务定位器在遗留应用程序中的使用可能有限,无法在应用程序中间引入组合根,以便从该点启动DI,但不是以您显示的方式启动DI.
如果父母需要孩子,那么只需注射它.现在问题是如果你想创建一个父,你首先需要孩子,但孩子需要父.怎么实现呢?有两种解决方案.然而,其中一个不是纯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.
Run Code Online (Sandbox Code Playgroud)
如果您不想使用工厂提供的容器,请自行明确注册.然后RegisterFactory调用替换为这个:
GlobalContainer.RegisterInstance<TFunc<IParentObject,IChildObject>>(
function(parent: IParentObject): IChildObject
begin
Result := GlobalContainer.Resolve<IChildObject>([TValue.From(parent)]);
end);
Run Code Online (Sandbox Code Playgroud)
并且构造函数参数可以更改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.
Run Code Online (Sandbox Code Playgroud)
顺便说说.使用接口时,请观察父和子之间的引用.如果他们互相引用,你会得到内存泄漏.您可以通过在一侧使用弱引用(通常是子代中的父引用)来解决此问题.
| 归档时间: |
|
| 查看次数: |
804 次 |
| 最近记录: |