c# - qué - ¿Son los parámetros del constructor primitivo una mala idea cuando se utiliza un contenedor IoC?
unity container c# (2)
Exención de responsabilidad como novato estándar: Soy nuevo en IoC y recibo señales mixtas. Estoy buscando orientación sobre la siguiente situación, por favor.
Supongamos que tengo la siguiente interfaz e 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 confunde si debería enviar solamente interfaces / dependencias al constructor, crear algún objeto de parámetro y pasarlo al constructor o mantenerlo como está y pasar los parámetros en el momento de resolver una instancia.
Entonces, ¿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 esté 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 incluso puede ajustarse a un Servicio de fachada (anteriormente llamado servicios agregados) como lo menciona Mark Seemann here .
También he leído que puedes usar propiedades para esos valores y usar la inyección de propiedades, aunque parece que ya no se prefiere (autofac menciona que se prefiere la inyección del constructor ... Ninject Creo que incluso eliminó la capacidad en la versión 2).
Alternativamente, 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 estoy 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.
Ahora me doy cuenta de que esta pregunta probablemente sea 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).
Bueno, terminé rediseñando esto después de leer el libro Dependency Injection en .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 ensamblado de Dominio:
public interface IImageFileService
{
void RenameFiles();
void CopyFiles();
}
public interface IImageLinkMapRepository
{
IList<ImageLink> GetImageLinks();
}
Luego, en un ensamblado de FileAccess, he creado implementaciones para 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
}
}
En esencia, 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. Utilicé los parámetros nombrados autofac para manejar inyectándolos.
Así que supongo que para responder mi propia pregunta, los parámetros del constructor primitivo son una buena idea si tienen sentido, pero asegúrese de ponerlos en el lugar al que pertenecen. Si las cosas no parecen estar funcionando correctamente, probablemente se pueda mejorar replanteando el diseño .
En su ejemplo, lo que realmente está pasando son dependencias , pero además datos que la clase necesita para 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 los métodos en una sola instancia de ImageFileGenerator
pueden ImageFileGenerator
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()
solo se llaman una vez con los mismos parámetros, los parámetros serían buenos candidatos para el constructor.
Personalmente, intentaría evitar la inyección de propiedades para las dependencias requeridas ; en ese caso, la inyección del constructor es mucho más apropiada.