delphi dependency-injection spring4d

delphi - Inyección de constructor versus inyección de colocador para propiedad de los padres



dependency-injection spring4d (1)

Estoy tratando de descubrir la mejor manera de usar la inyección de dependencia para algún código heredado que tardará en refactorizarse y debe hacerse gradualmente. La mayoría de las clases antiguas usan una propiedad "principal" para determinar varias cosas y la propiedad principal a menudo pasa a través de un argumento constructor de la siguiente manera:

constructor TParentObject.Create; begin FChildObject := TChildObject.Create(Self); end; constructor TChildObject.Create(AParent: TParentObject) begin FParent := AParent; end;

Esto es bastante típico de nuestra base de código heredado. Sin embargo, cuando se mueve a interfaces e inyección de constructor, el marco Spring4D no conoce al elemento primario al crear el objeto Child. Por lo tanto, solo obtendrá un nuevo padre pero no el existente. Por supuesto, puedo crear un getter / setter de propiedad, pero esto indicaría una propiedad "opcional" para la clase, que en realidad es una propiedad obligatoria. Vea el código a continuación para más explicación:

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.

Tal vez hay alguna metodología que se puede utilizar y que no conozco para establecer el objeto padre usando el marco DI.

Espero que esta pregunta sea clara en lo que intento lograr. Me complace proporcionar más ejemplos de descripción / código cuando sea necesario.


En primer lugar, no debe usar el localizador de servicios para reemplazar las llamadas de ctor. Esto solo empeora las cosas. Sé que la gente piensa que son inteligentes haciendo eso, pero realmente estás reemplazando una dependencia simple en otra clase con una dependencia en algún estado global más el requisito de que algún otro código fuera del control (las clases consumidoras) coloque la dependencia en el contenedor. Eso no resulta en un código más fácil pero más difícil de mantener.

Además de todas las otras razones por las que debe mantenerse alejado de ella. El localizador de servicios puede tener un uso limitado en la aplicación heredada para introducir una raíz de composición en el medio de una aplicación para iniciar la DI a partir de ese momento, pero no de la manera que se muestra.

Si el padre necesita al niño, solo inyéctelo. Ahora el problema es si desea crear un padre, primero necesita al niño pero el niño necesita al padre. ¿Cómo lograr eso? Hay dos soluciones. Sin embargo, uno de ellos no es DI puro compatible.

Primero muestro el camino usando una fábrica provista por el contenedor (necesita la última versión de rama de desarrollo a partir del momento de la publicación):

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.

Si no desea utilizar una fábrica provista por un contenedor, explícitamente regístrese usted mismo. Luego, la llamada de RegisterFactory se reemplaza por esta:

GlobalContainer.RegisterInstance<TFunc<IParentObject,IChildObject>>( function(parent: IParentObject): IChildObject begin Result := GlobalContainer.Resolve<IChildObject>([TValue.From(parent)]); end);

Y el parámetro constructor se puede cambiar a TFunc<...> ya que no necesita RTTI para este método (es por eso que necesitó IFactory<...> en el otro caso).

La segunda versión usa inyección de campo y, por lo tanto, es DI pura incompatible. Tenga cuidado al escribir código como este ya que no funciona sin usar el contenedor o RTTI. Por ejemplo, si quiere probar estas clases puede ser difícil componerlas sin el contenedor. La parte importante aquí es PerResolve, que le dice al contenedor que reutilice la instancia una vez resuelta cada vez que se necesite otra dependencia que pueda satisfacer.

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.

Por cierto. Observe sus referencias entre padres e hijos cuando usa interfaces. Si se referencian entre sí obtendrán pérdidas de memoria. Puede resolver eso utilizando una referencia débil en un lado (generalmente la referencia principal en el niño).