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


Estoy tratando de 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 manera en que se me ocurre 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 Unidad) 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 basado en la entrada del usuario. El caso trivial aquí simplemente devuelve el valor de runTimeParam pero en realidad el parámetro será algo así como nombre de archivo e inicializar método 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 un indicador en la implementación y lanzar una excepción en una llamada repetida a Initialize parece muy torpe.

En el punto donde resuelvo mi interfaz no quiero saber nada sobre la implementación de IMyIntf. Lo que quiero, sin embargo, es el conocimiento de que esta interfaz necesita ciertos parámetros de inicialización de una sola vez. ¿Hay una manera de anotar de alguna manera(atributos?) la interfaz con esta información y pasar a marco cuando se crea el objeto?

Editar: Describe la interfaz un poco más.

Author: Blorgbeard, 2009-12-22

5 answers

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 una Abstracción con fugas .

En su caso, yo diría que debe modelar la interfaz IMyIntf en cómo necesita usarla - no cómo intenta crear implementaciones de la misma. Es un detalle de implementación.

Por lo tanto, la interfaz debe simplemente be:

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 los invariantes de la clase mediante el uso de la palabra clave readonly. No son necesarios métodos de inicialización malolientes.

Una implementación 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, usted simplemente tome una dependencia en IMyIntfFactorysolicitándola a través de Inyección de constructor.

Cualquier contenedor DI que valga la pena será capaz de auto-cablear una instancia IMyIntfFactory para usted si lo registra correctamente.

 257
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-29 02:08:08

Por lo general, cuando se encuentra con esta situación, debe revisar su diseño y determinar si está mezclando sus objetos con estado/datos con sus servicios pure. En la mayoría de los casos (no en todos), querrá mantener estos dos tipos de objetos separados.

Si necesita que se pase un parámetro específico del contexto 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 en tiempo de ejecución como parámetro de la creación() método (o Generate (), Build () o como llames tus métodos de fábrica).

Tener setters o un método Initialize() generalmente se cree que es un mal diseño, ya que necesita "recordar" llamarlos y asegurarse de que no abran demasiado el estado de su implementación (es decir, ¿qué es evitar que alguien vuelva a llamar a initialize o al setter?).

 13
Author: Phil Sandler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-22 03:03:57

También me he encontrado con esta situación algunas veces en entornos donde estoy creando dinámicamente objetos ViewModel basados en objetos de modelo (delineados muy bien por este otro post de Stackoverflow).

Me gustó cómo la extensión Ninject que le permite crear dinámicamente fábricas basadas en interfaces:

Bind<IMyFactory>().ToFactory();

No pude encontrar ninguna funcionalidad similar directamente en Unity ; así que escribí mi propia extensión a la IUnityContainer que le permite registrar fábricas que crearán nuevos objetos basados en datos de objetos existentes esencialmente mapeando 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 de fábrica individuales o interfaces (un ahorro de tiempo real). Solo tienes que añadir las asignaciones justo donde registre las clases durante el proceso normal de bootstrapping...

//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>();

Luego simplemente declare la interfaz de fábrica de asignación en el constructor para CI y use 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 asignadas 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.

 5
Author: jigamiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:10

No puedo responder con terminología específica de Unity, pero parece que estás aprendiendo sobre la inyección de dependencias. Si es así, le insto a que lea la breve, clara y llena de información guía del usuario para Ninject.

Esto le 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 usar 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 usando atributos para distinguirlos en 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 frameworks DI (o convencerte de que le des una oportunidad a Ninject).

 1
Author: anthony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-22 00:56:35

Creo que lo resolví y se siente bastante saludable, por lo que debe ser medio correcto:))

Divido IMyIntf en interfaces "getter" y "setter". Entonces:

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 llamar varias veces, pero usando bits de Service Locator paradigm podemos envolverlo bastante bien para que IMyIntfSetter sea casi una interfaz interna que es distinta de IMyIntf.

 1
Author: Igor Zevaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-22 02:08:31