¿Existe un patrón para inicializar objetos creados a través de un contenedor DI?

Resuelto Igor Zevaka asked hace 15 años • 5 respuestas

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, runTimeParamel 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 runTimeParampero 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 Initializemé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 Initializeparece 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.

Igor Zevaka avatar Dec 22 '09 07:12 Igor Zevaka
Aceptado

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 IMyIntfinterfaz 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 IMyIntfFactoryque cree instancias concretas IMyIntfcomo 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 readonlypalabra clave. No se necesitan métodos de inicialización malolientes.

Una IMyIntfFactoryimplementació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 IMyIntfinstancia, simplemente toma una dependencia IMyIntfFactorysolicitándola a través de Constructor Inyección .

Cualquier contenedor DI que se precie podrá conectar automáticamente una IMyIntfFactoryinstancia si la registra correctamente.

Mark Seemann avatar Dec 22 '2009 08:12 Mark Seemann

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.

jigamiller avatar Jul 11 '2013 19:07 jigamiller