33

standard newbie disclaimer: Sono nuovo di CIO e sono sempre segnali contrastanti. Sto cercando alcune indicazioni sulla seguente situazione per favore.Sono parametri del costruttore primitive una cattiva idea quando si utilizza un IoC Container?

Supponiamo che io ho la seguente interfaccia e l'implementazione:

public interface IImageFileGenerator 
{ 
    void RenameFiles(); 
    void CopyFiles(); 
} 

public class ImageFileGenerator : IImageFileGenerator 
{ 
    private readonly IList<IImageLink> _links; 
    private readonly string _sourceFolder; 
    private readonly string _destinationFolder; 
    private readonly int _folderPrefixLength; 

    public ImageFileGenerator(IList<IImageLink> links, string sourceFolder, string destinationFolder) 
    { 
     _links = links; 
     _sourceFolder = sourceFolder; 
     _destinationFolder = destinationFolder; 
     _folderPrefixLength = 4; 
    } 

    public void RenameFiles() 
    { 
     // Do stuff, uses all the class fields except destination folder 
    } 

    public void CopyFiles() 
    { 
     // Do stuff, also uses the class fields 
    } 
} 

sto ottenendo confuso se devo inviare solo di interfaccia/dipendenze al costruttore, creare qualche oggetto parametro e passarlo al costruttore o tenerlo così com'è e passa i parametri al momento della risoluzione di un'istanza.

Quindi c'è un modo più corretto di creare questo codice per funzionare al meglio con un contenitore IoC? Una delle seguenti cose sarebbe preferibile in termini di design rispetto al mio attuale layout?

1.

public interface IImageFileGenerator 
{ 
    void RenameFiles(IList<IImageLink> links, string sourceFolder); 
    void CopyFiles(IList<IImageLink> links, string sourceFolder, stringDestFolder); 
} 

public class ImageFileGenerator : IImageFileGenerator 
{ 
    private readonly int _folderPrefixLength; 

    public ImageFileGenerator() 
    { 
     _folderPrefixLength = 4; 
    } 

    public void RenameFiles(IList<IImageLink> links, string sourceFolder) 
    { 
     // Do stuff 
    } 

    public void CopyFiles(IList<IImageLink> links, string sourceFolder, stringDestFolder) 
    { 
     // Do stuff 
    } 
} 

Non mi piace che sto passando la stessa cosa in entrambi i casi (tranne la cartella di destinazione). Nella implementazione corrente del IImageFileGenerator, devo eseguire entrambi i metodi e sono stati necessari gli stessi valori per ciascun metodo. Ecco perché ho passato lo stato in via del costruttore.

2.

public interface IImageFileGenerator 
{ 
    void RenameFiles(); 
    void CopyFiles(); 
} 

public class ImageLinkContext 
{ 
    // various properties to hold the values needed in the 
    // ImageFileGenerator implementation. 
} 

public class ImageFileGenerator : IImageFileGenerator 
{ 
    private readonly IList<IImageLink> _links; 
    private readonly string _sourceFolder; 
    private readonly string _destinationFolder; 
    private readonly int _folderPrefixLength; 

    public ImageFileGenerator(ImageLinkContext imageLinkContext) 
    { 
     // could also use these values directly in the methods 
     // by adding a single ImageLinkContext field and skip 
     // creating the other fields 
     _links = imageLinkContext.ImageLinks; 
     _sourceFolder = imageLinkContext.Source; 
     _destinationFolder = imageLinkContext.Destination; 
     _folderPrefixLength = 4; 
    } 

    public void RenameFiles() 
    { 
     // Do stuff, uses all the class fields except destination folder 
    } 

    public void CopyFiles() 
    { 
     // Do stuff, uses all the class fields 
    } 
} 

Questo approccio può anche essere ottimizzato per un Servizio di facciata (precedentemente chiamato i servizi di aggregazione) come detto da Mark Seemann here.

Ho anche letto che è possibile utilizzare le proprietà per questi valori e utilizzare l'iniezione di proprietà, anche se sembra che non sia più preferito (le dichiarazioni di autofac è preferibile l'iniezione del costruttore ... Ninject credo che abbia rimosso anche l'abilità nella versione 2).

In alternativa ho letto che è anche possibile creare un metodo di inizializzazione e assicurarsi che le proprietà siano impostate lì.

così tante opzioni e sto diventando più confuso come ho letto di più su questa roba. Sono sicuro che non c'è modo corretto definitiva (o forse non v'è, almeno per questo esempio ???), ma forse qualcuno può fornire pro ei contro di ogni approccio. O forse c'è un altro approccio che mi è totalmente mancato.

Mi rendo conto ora che questa domanda è probabilmente un po 'sul lato soggettivo (ed è davvero più di una domanda), ma spero che tu possa perdonarmi e fornire una guida.

PS - Attualmente sto provando la mia mano con autofac nel caso in cui ciò influenzi il design che potrebbe adattarsi meglio.

NOTA: ho apportato una leggera modifica al codice relativo alla cartella di destinazione ... non è utilizzato da RenameFiles (potrebbe avere un impatto sulla risposta).

+1

Ecco una discussione correlata sull'iniezione di * servizi * vs * dati *: http://stackoverflow.com/questions/1818539/how-to-pass-controllers-modelstate-to-my-service-constructor-with-autofac –

+0

@Peter Lillevold: Interessante, passerò un po 'di tempo a guardare i delegati della fabbrica. Posso vederli tornare utile. Grazie per il link (e l'articolo allegato nella risposta: http://peterspattern.com/generate-generic-factories-with-autofac/). –

risposta

17

Bene, ho finito per ridisegnare questo dopo aver letto il libro Dependency Injection in .Net (consiglio vivamente questo libro a qualsiasi sviluppatore orientato agli oggetti, non solo agli sviluppatori .Net e non solo a coloro che sono interessati ad usare un contenitore IoC!).

ora ho il seguente in un assieme di dominio:

public interface IImageFileService 
{ 
    void RenameFiles(); 
    void CopyFiles(); 
} 

public interface IImageLinkMapRepository 
{ 
    IList<ImageLink> GetImageLinks(); 
} 

Poi, in un assemblaggio FileAccess ho creato implementazioni per queste interfacce:

public class ImageFileService : IImageFileService 
{ 
    public ImageFileService(IImageLinkMapRepository repository) 
    { 
     // null checks etc. left out for brevity 
     _repository = repository; 
    } 

    public void RenameFiles() 
    { 
     // rename files, using _repository.GetImageLinks(), which encapsulates 
     // enough information for it to do the rename operations without this 
     // class needing to know the specific details of the source/dest dirs. 
    } 

    public void CopyFiles() 
    { 
     // same deal as above 
    } 
} 

Quindi, in sostanza, ho rimosso la necessità di tipi primitivi nel mio costruttore, almeno per questa classe. Ad un certo punto ho avuto bisogno di quell'informazione, ma questa è stata iniettata nel Repository ImageLinkMap dove le informazioni avevano più senso. Ho usato autofac named parameters per gestire l'iniezione di essi.

Quindi, suppongo di rispondere alla mia stessa domanda, i parametri del costruttore primitivo sono una buona idea se hanno senso, ma assicurati di metterli a posto. Se le cose sembrano non funzionare correttamente, può essere probabilmente migliorato ripensando il design.

2

Nel tuo esempio cosa si sta effettivamente passando sono dipendenze, ma soprattutto dati necessari dalla classe per operare.

Nel tuo caso sembra che i metodi RenameFiles() e CopyFiles() operino sui parametri che vengono loro trasmessi.Dato il loro nome , penso che sia possibile chiamare i metodi su una singola istanza di ImageFileGenerator con parametri diversi. Se ciò è vero, probabilmente i parametri dovrebbero essere sul metodo stesso non chiama il costruttore.

Se, d'altra parte, su un'istanza RenameFiles() e CopyFiles() vengono chiamati ciascuno solo una volta con gli stessi parametri, i parametri potrebbero essere buoni candidati per il costruttore.

Io personalmente proverei a evitare l'iniezione di proprietà per le dipendenze necessarie a - in questo caso l'iniezione del costruttore è molto più appropriata.

+0

L'utilizzo corrente è che entrambi vengono chiamati una sola volta e con gli stessi valori di parametro. Ed è * probabile * che chiameremo sempre ** RenameFiles **, e subito dopo, chiamando ** CopyFiles **. Suppongo che potrei combinarli in un unico metodo pubblico e quindi renderli entrambi privati. Stavo cercando di suddividere le funzionalità per semplificare i test delle unità. –

+0

@ JasonDown: In tal caso, penso che tu abbia risposto alla tua stessa domanda. Inoltre, se c'è davvero una sola operazione sui dati che passi puoi semplicemente spostarli in un metodo statico. – BrokenGlass

+0

Sicuramente qualcosa da considerare. Pensando al carico ... ci sono davvero buone probabilità che sia necessaria un'altra implementazione dell'interfaccia che copi solo le immagini ... e avrà una sorgente e una destinazione diversa (sarebbe * copiando * le immagini, ma le copie sarebbe convertito in una dimensione di anteprima). Quindi forse passare i parametri in ogni metodo è la strada da percorrere. –