dependency-injection - injection - inyeccion de dependencias spring
¿Hay un patrón para inicializar objetos creados a través de un contenedor DI? (5)
Creo que lo resolví y se siente bastante saludable, por lo que debe ser a la mitad :)
IMyIntf
en una IMyIntf
"getter" y "setter". Asi que:
interface IMyIntf {
string RunTimeParam { get; }
}
interface IMyIntfSetter {
void Initialize(string runTimeParam);
IMyIntf MyIntf {get; }
}
Luego la implementación:
class MyIntfImpl : IMyIntf, IMyIntfSetter {
string _runTimeParam;
void Initialize(string runTimeParam) {
_runTimeParam = runTimeParam;
}
string RunTimeParam { get; }
IMyIntf MyIntf {get {return this;} }
}
//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;
IMyIntfSetter.Initialize()
todavía se puede IMyIntfSetter.Initialize()
varias veces, pero usando bits del paradigma de Localizador de servicios podemos envolverlo bastante bien para que IMyIntfSetter
sea casi una interfaz interna distinta de IMyIntf
.
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 podría pensar en la forma de hacerlo es tener un método Init en la interfaz.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Entonces para usarlo (en Unity) haría esto:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
En este escenario, runTimeParam
param 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 método Initialize
está disponible en la interfaz y se puede llamar varias veces. Establecer una bandera en la implementación y lanzar una excepción en una llamada repetida para Initialize
parece demasiado complicado.
En el momento en que resuelvo mi interfaz, no quiero saber nada sobre la implementación de IMyIntf
. Lo que sí quiero, sin embargo, es el conocimiento de que esta interfaz necesita ciertos parámetros de inicialización de una sola vez. ¿Hay alguna forma de anotar (atributos) 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 Initialize métodos en las interfaces huele a Leaky Abstraction .
En su caso, diría que debe modelar la interfaz IMyIntf
sobre cómo debe usarla, y no cómo IMyIntf
crear implementaciones de la misma. Eso es un detalle de implementación.
Por lo tanto, la interfaz debería ser simplemente:
public interface IMyIntf
{
string RunTimeParam { get; }
}
Ahora define la fábrica abstracta:
public interface IMyIntfFactory
{
IMyIntf Create(string runTimeParam);
}
Ahora puede crear una implementación concreta de IMyIntfFactory
que cree instancias concretas de 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 palabra clave readonly
. No se necesitan métodos de inicialización malolientes.
Una implementación de IMyIntfFactory
puede ser tan simple como esto:
public class MyIntfFactory : IMyIntfFactory
{
public IMyIntf Create(string runTimeParam)
{
return new MyIntf(runTimeParam);
}
}
En todos sus consumidores donde necesita una instancia IMyIntf
, simplemente toma una dependencia de IMyIntfFactory
solicitándolo a través de Constructor Injection .
Cualquier contenedor DI que valga la pena será capaz de IMyIntfFactory
automáticamente una instancia de IMyIntfFactory
si la registra correctamente.
No puedo responder con la terminología específica de Unity, pero parece que estás aprendiendo sobre la inyección de dependencia. Si es así, le pido que lea la guía breve, clara y llena de información del usuario para Ninject .
Esto lo guiará a través de las diversas opciones que tiene al usar DI, y cómo dar cuenta de los problemas específicos que enfrentará en el camino. En su caso, lo más probable es que desee utilizar el contenedor DI para crear instancias de sus objetos, y hacer que ese objeto obtenga una referencia a cada una de sus dependencias a través del constructor.
El tutorial también detalla cómo anotar métodos, propiedades e incluso parámetros utilizando atributos para distinguirlos en el tiempo de ejecución.
Incluso si no usas Ninject, el tutorial te dará los conceptos y la terminología de la funcionalidad que se adapte a tu propósito, y deberías ser capaz de asignar ese conocimiento a Unity u otros marcos DI (o convencerte para que pruebes Ninject) .
Por lo general, cuando se encuentra con esta situación, necesita volver a visitar su diseño y determinar si está mezclando sus objetos con estado / datos con sus servicios puros. En la mayoría de los casos (no todos), querrá mantener separados estos dos tipos de objetos.
Si necesita un parámetro específico del contexto pasado en el constructor, una opción es crear una fábrica que resuelva sus dependencias de servicio a través del constructor, y tome su parámetro de tiempo de ejecución como un parámetro del método Create () (o Generar ( ), Build () o como quiera que nombre sus métodos de fábrica).
En general, se considera que los setters o el método Initialize () son un mal diseño, ya que debe "recordar" llamarlos y asegurarse de que no abren demasiado el estado de su implementación (es decir, qué es lo que impide que alguien vuelva). -llamar inicializar o el colocador?).
También me he encontrado con esta situación varias veces en entornos en los que estoy creando dinámicamente objetos ViewModel basados en objetos Model (descritos muy bien por esta otra publicación de ).
Me gustó cómo 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 en IUnityContainer, que le permite registrar fábricas que crearán nuevos objetos basados en datos de objetos existentes, mapeando esencialmente desde una jerarquía de tipos a una jerarquía de tipos diferente: UnityMappingFactory@GitHub
Con un objetivo de simplicidad y legibilidad, terminé con una extensión que le permite especificar directamente las asignaciones sin declarar clases o interfaces de fábrica individuales (un ahorro de tiempo real). Simplemente agrega las asignaciones justo donde registras las clases durante el proceso de arranque normal ...
//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>();
Entonces usted declara la interfaz de fábrica de mapeo en el constructor para CI y usa su método Create () ...
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));
}
Como una ventaja adicional, cualquier dependencia adicional en el constructor de las clases mapeadas también se resolverá durante la creación del objeto.
Obviamente, esto no resolverá todos los problemas, pero me ha servido bastante bien hasta ahora, así que pensé que debería compartirlo. Hay más documentación en el sitio del proyecto en GitHub.