2015-08-11 16 views
12

Sto cercando di capire il modo migliore per utilizzare l'integrazione delle dipendenze per un codice precedente che richiederà un lungo refactoring e che deve essere eseguito gradualmente. La maggior parte delle vecchie classi utilizzare una proprietà "Parent" per determinare varie cose e la proprietà genitore è stato spesso passano in via un argomento del costruttore come segue:Iniezione del costruttore rispetto all'iniezione setter per proprietà Parent

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

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

Questo è abbastanza tipico della nostra base di codice legacy. Tuttavia, quando si passa alle interfacce e all'iniezione del costruttore, il Parent non è conosciuto dal framework Spring4D quando crea l'oggetto Child. Quindi otterrà solo un nuovo genitore ma non quello esistente. Naturalmente posso creare un getter/setter di proprietà, ma ciò indicherebbe una proprietà "facoltativa" per la classe che è in realtà una proprietà obbligatoria. Vedere il codice qui sotto per ulteriori spiegazioni:

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. 

Forse c'è qualche metodo che può essere utilizzato che io non sono a conoscenza di impostare l'oggetto padre utilizzando il framework DI?

Spero che questa domanda sia chiara su cosa sto cercando di ottenere. Sono felice di fornire ulteriori esempi di descrizione/codice dove necessario.

+0

Sarebbe interessante vedere il codice che "crea" effettivamente gli oggetti figli. Sento odore di localizzatore di servizi. –

+0

Haha Stefan, hai ragione, è quello che stavo progettando di usare ma mi chiedevo se c'è un'alternativa? Pubblicherò il codice ora. –

+1

La mia domanda sarebbe: perché pensi che sia meglio che chiamare direttamente il costruttore di TChildObject in TParentObject? Immagino che il codice reale sia un po 'più complesso, ma comunque: se hai una relazione genitore/figlio le classi potrebbero comunque conoscersi. In caso contrario, suggerirei di utilizzare il modello di fabbrica. Pubblicherà del codice. –

risposta

14

Prima di tutto non è necessario utilizzare il localizzatore di servizio per sostituire le chiamate del medico. Questo sta solo peggiorando le cose. So che le persone pensano di essere intelligenti, ma in realtà si sta sostituendo una dipendenza semplice con un'altra classe con una dipendenza da qualche stato globale più il requisito che qualche altro codice di controllo (le classi consumatrici) inserisca la dipendenza nel contenitore. Ciò non risulta in un codice più facile ma più difficile da mantenere.

Inoltre tutti i other reasons perché si dovrebbe stare lontano da esso. Il localizzatore di servizi potrebbe avere un uso limitato nell'applicazione legacy per introdurre una radice di composizione nel mezzo di un'applicazione per avviare DI da quel punto in poi, ma non in modo da mostrare.

Se il genitore ha bisogno del bambino, lo si inietta. Ora il problema è che se vuoi creare un genitore, prima hai bisogno del bambino, ma il bambino ha bisogno del genitore. Come raggiungerlo? Ci sono due soluzioni. Tuttavia uno di questi non è compatibile con lo standard pure DI.

mostro prima il modo in cui utilizza una fabbrica fornita dal contenitore (ha bisogno di più recente sviluppo la versione filiale a partire dal momento del distacco):

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. 

Se non si desidera utilizzare un contenitore fornito fabbrica si registralo esplicitamente da solo. Poi la chiamata RegisterFactory viene sostituito con questo:

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

E il parametro del costruttore può essere modificata per TFunc<...> in quanto non ha bisogno di RTTI per questo metodo (che è il motivo per cui avevi bisogno IFactory<...> nell'altro caso).

La seconda versione utilizza l'iniezione di campo e quindi è DI incompatibile puro: sii scrupoloso di scrivere codice come quello in quanto non funziona senza utilizzare il contenitore o RTTI, ad esempio se si desidera verificare queste classi potrebbe diventare difficile comporle senza il contenitore. La parte importante qui è il PerResolve che dice al contenitore di riutilizzare l'istanza una volta risolta ogni volta che è necessaria un'altra dipendenza che può soddisfare.

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. 

A proposito. Guarda i tuoi riferimenti tra genitore e figlio quando usi le interfacce. Se si fanno riferimento l'un l'altro si otterranno perdite di memoria.Puoi risolverlo usando un riferimento debole su un lato (di solito il riferimento genitore nel bambino).

+0

Grazie per l'esteso post Stefan - questo genitore/figlio sarebbe davvero utile negli esempi Spring4D. Utilizziamo DSharp.Mocks in modo abbastanza estensivo, quindi sarà necessario giocare e decidere quale sia l'approccio migliore. Sono d'accordo con il tuo commento sull'utilizzo di ServiceLocator per sostituire Ctor's, ma non sapevo cos'altro fare. C'è qualche circostanza in cui pensi che sia "ok" usare il ServiceLocator, o non è mai ok? Ho notato che usi GlobalContainer.Resolve invece di ServiceLocator: potresti descrivere la differenza? Grazie ancora, il tuo continuo lavoro di comunità non è secondo a nessuno. –

+0

Sto provando a creare l'ultimo ramo di sviluppo ma continua a non riuscire: Spring.Container.CreationContext.pas (108): errore E2033: I tipi di parametri var effettivi e formali devono essere identici –

+1

Dovrebbe funzionare ora. Per quanto riguarda il localizzatore di servizi. Solitamente risolvo dal contenitore direttamente poiché la posizione in cui ciò accade non è profonda nell'applicazione (cioè la radice di composizione). Vi consiglio di non usare ServiceLocator - in un'architettura ben progettata non ha alcun posto. Nell'applicazione legacy potrebbe avere un posto temporaneo ma, come ho detto, potrebbe peggiorare le cose e non migliorare. –

Problemi correlati