¿Existe un patrón para inicializar objetos creados a través de un contenedor DI?
Estoy intentando que Unity administre la creación de mis objetos y quiero tener algunos parámetros de inicialización que no se conocen hasta el tiempo de ejecución:
Por el momento, la única forma en que se me ocurre hacerlo es tener un método Init en la interfaz.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Luego para usarlo (en Unity) haría esto:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
En este escenario, runTimeParam
el parámetro se determina en tiempo de ejecución en función de la entrada del usuario. El caso trivial aquí simplemente devuelve el valor de runTimeParam
pero en realidad el parámetro será algo así como el nombre del archivo y el método de inicialización hará algo con el archivo.
Esto crea una serie de problemas, a saber, que el Initialize
método está disponible en la interfaz y se puede llamar varias veces. Establecer una bandera en la implementación y lanzar una excepción en llamadas repetidas Initialize
parece muy complicado.
En el momento en que resuelvo mi interfaz, no quiero saber nada sobre la implementación de IMyIntf
. Sin embargo, lo que sí quiero es saber que esta interfaz necesita ciertos parámetros de inicialización únicos. ¿Hay alguna manera de anotar (¿atributos?) de alguna manera la interfaz con esta información y pasarla al marco cuando se crea el objeto?
Editar: describió la interfaz un poco más.
En cualquier lugar donde necesite un valor de tiempo de ejecución para construir una dependencia particular, Abstract Factory es la solución.
Tener métodos de inicialización en las interfaces huele a abstracción con fugas .
En su caso, diría que debe modelar la IMyIntf
interfaz según cómo necesita usarla , no cómo pretende crear implementaciones de la misma. Ese es un detalle de implementación.
Por tanto, la interfaz debería ser simplemente:
public interface IMyIntf
{
string RunTimeParam { get; }
}
Ahora defina la Fábrica Abstracta:
public interface IMyIntfFactory
{
IMyIntf Create(string runTimeParam);
}
Ahora puede crear una implementación concreta IMyIntfFactory
que cree instancias concretas IMyIntf
como esta:
public class MyIntf : IMyIntf
{
private readonly string runTimeParam;
public MyIntf(string runTimeParam)
{
if(runTimeParam == null)
{
throw new ArgumentNullException("runTimeParam");
}
this.runTimeParam = runTimeParam;
}
public string RunTimeParam
{
get { return this.runTimeParam; }
}
}
Observe cómo esto nos permite proteger las invariantes de la clase mediante el uso de la readonly
palabra clave. No se necesitan métodos de inicialización malolientes.
Una IMyIntfFactory
implementación puede ser tan simple como esta:
public class MyIntfFactory : IMyIntfFactory
{
public IMyIntf Create(string runTimeParam)
{
return new MyIntf(runTimeParam);
}
}
En todos sus consumidores donde necesita una IMyIntf
instancia, simplemente toma una dependencia IMyIntfFactory
solicitándola a través de Constructor Inyección .
Cualquier contenedor DI que se precie podrá conectar automáticamente una IMyIntfFactory
instancia si la registra correctamente.
También me he encontrado con esta situación varias veces en entornos donde estoy creando dinámicamente objetos ViewModel basados en objetos Modelo (muy bien descrito en esta otra publicación de Stackoverflow ).
Me gustó la extensión Ninject que te permite crear fábricas dinámicamente basadas en interfaces:
Bind<IMyFactory>().ToFactory();
No pude encontrar ninguna funcionalidad similar directamente en Unity ; así que escribí mi propia extensión para IUnityContainer que le permite registrar fábricas que crearán nuevos objetos basados en datos de objetos existentes, esencialmente mapeando de una jerarquía de tipos a una jerarquía de tipos diferente: UnityMappingFactory@GitHub
With a goal of simplicity and readability, I ended up with an extension that allows you to directly specify the mappings without declaring individual factory classes or interfaces (a real time saver). You just add the mappings right where you register the classes during the normal bootstrapping process...
//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();
//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();
Then you just declare the mapping factory interface in the constructor for CI and use its Create() method...
public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }
public TextWidgetViewModel(ITextWidget widget) { }
public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
foreach (IWidget w in data.Widgets)
children.Add(factory.Create(w));
}
As an added bonus, any additional dependencies in the constructor of the mapped classes will also get resolved during object creation.
Obviously, this won't solve every problem but it has served me pretty well so far so I thought I should share it. There is more documentation on the project's site on GitHub.