33

Descargo de responsabilidad estándar para novatos: Soy nuevo en IoC y estoy recibiendo señales mixtas. Estoy buscando orientación sobre la siguiente situación, por favor.¿Los parámetros de constructor primitivo son una mala idea cuando se utiliza un contenedor de IoC?

Supongamos que tengo la siguiente interfaz y la implementación:

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 
    } 
} 

Me estoy confundido si sólo debería enviar interfaz/dependencias con el constructor, cree un objeto de parámetro y pasarlo al constructor o mantenerlo como está y pasa los parámetros en el momento de resolver una instancia.

¿Hay una forma más correcta de configurar este código para que funcione mejor con un contenedor IoC? ¿Sería preferible alguna de las siguientes opciones de diseño sobre mi diseño actual?

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 
    } 
} 

no me gusta que estoy pasando exactamente lo mismo en ambos casos (excepto la carpeta de destino). En la implementación actual de IImageFileGenerator, necesito ejecutar ambos métodos y se necesitan los mismos valores para cada método. Es por eso que pasé el estado en el constructor.

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 
    } 
} 

Este enfoque puede incluso ser ajustado a un Servicio de Fachada (anteriormente llamado servicios agregados) como se ha mencionado por Mark Seemann here.

También he leído que podría usar propiedades para esos valores y usar inyección de propiedad, aunque parece que ya no se prefiere (autofac menciona que la inyección del constructor es preferible ... Ninject Creo que incluso eliminó la capacidad en la versión 2).

Como alternativa, he leído que también puede crear un método de inicialización y asegurarse de que las propiedades estén establecidas allí.

Tantas opciones y cada vez me siento más confundido a medida que leo más sobre esto. Estoy seguro de que no hay una forma definitiva y correcta (¿o tal vez lo hay, al menos para este ejemplo?), Pero tal vez alguien pueda proporcionar los pros y los contras de cada enfoque. O tal vez hay otro enfoque que me he perdido totalmente.

Me doy cuenta ahora de que esta pregunta es, probablemente, un poco subjetiva (y en realidad es más de una pregunta), pero espero que me puedas perdonar y brindar alguna orientación.

PD - Actualmente estoy probando mi mano con autofac en caso de que influya en qué diseño puede caber mejor.

NOTA: He realizado un ligero cambio en el código sobre la carpeta de destino ... RenameFiles no lo usa (puede influir en su respuesta).

+1

Aquí hay una discusión relacionada sobre cómo inyectar * servicios * vs * datos *: http://stackoverflow.com/questions/1818539/how-to-pass-controllers-modelstate-to-my-service-constructor-with-autofac –

+0

@Peter Lillevold: Interesante, voy a pasar un tiempo mirando a los delegados de la fábrica. Veo que son útiles. Gracias por el enlace (y el artículo adjunto en su respuesta: http://peterspattern.com/generate-generic-factories-with-autofac/). –

Respuesta

17

Bueno, terminé rediseñando esto después de leer el libro Dependency Injection in .Net (Recomiendo este libro a cualquier desarrollador orientado a objetos, no solo a los desarrolladores de .Net y no solo a aquellos interesados ​​en utilizar un contenedor IoC).

ahora tengo lo siguiente en un montaje de Dominio:

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

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

Luego, en un montaje de FileAccess He creado implementaciones de estas interfaces:

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 
    } 
} 

Así que, esencialmente, he eliminé la necesidad de tipos primitivos en mi constructor, al menos para esta clase. En algún momento necesité esa información, pero eso se inyectó en ImageLinkMapRepository, donde la información tenía más sentido. Usé autofac named parameters para manejar inyectándolos.

Así que supongo que para responder a mi propia pregunta, los parámetros del constructor primitivo son una buena idea si tienen sentido, pero asegúrese de ponerlos donde pertenecen. Si las cosas no parecen funcionar correctamente, probablemente se pueda mejorar repensando el diseño.

2

En su ejemplo lo que está pasando en realidad son dependencias, sino que además datos requeridos por la clase de operar.

En su caso, parece que los métodos RenameFiles() y CopyFiles() operan en los parámetros que se les pasan.Dados sus nombres creo que que los métodos en una sola instancia de ImageFileGenerator se pueden llamar con diferentes parámetros. Si eso es cierto, los parámetros deberían estar en el método no se llama el constructor.

Si, por otro lado, en una instancia RenameFiles() y CopyFiles() se llaman una sola vez con los mismos parámetros, los parámetros serían buenos candidatos para el constructor.

Yo personalmente trataría de evitar la inyección de propiedad para dependencias requeridas - en ese caso, la inyección del constructor es mucho más apropiada.

+0

El uso actual es que ambos se invocan solo una vez y con los mismos valores de parámetro. Y es * probable * que siempre llamaremos ** RenameFiles **, e inmediatamente después, llamando a ** CopyFiles **. Supongo que podría combinarlos en un solo método público y luego hacer que ambos sean privados. Estaba tratando de dividir la funcionalidad para una prueba de unidad más fácil. –

+0

@JasonDown: en ese caso, creo que respondió su propia pregunta. Además, si realmente solo hay una operación en los datos que usted ingresa, puede mover esto a un método estático. – BrokenGlass

+0

Definitivamente algo a considerar. Pensando en la carga ... hay buenas posibilidades de que se necesite otra implementación de la interfaz que solo copie imágenes ... y tendrá una fuente y un destino diferentes (sería * copiar * las imágenes, pero las copias se convertiría a un tamaño de miniatura). Entonces tal vez pasar los parámetros a cada método es el camino a seguir. –

Cuestiones relacionadas