6

Estoy escribiendo una biblioteca que proporcionará una colección de tipos públicos a sus consumidores.Valores predeterminados para argumentos de constructor en un proyecto de biblioteca

Quiero que los tipos de esta biblioteca sean compatibles con la inyección. Esto significa que cada clase necesita tener un constructor a través del cual es posible especificar cada dependencia del objeto que se inicializa. También quiero que la biblioteca se adhiera a la convención sobre el principio de configuración. Esto significa que si un consumidor desea el comportamiento predeterminado, puede usar un constructor sin parámetros y el objeto de alguna manera construirá las dependencias por sí mismo.

En el ejemplo (C#):

public class Samurai { 

    private readonly IWeapon _weapon; 

    // consumers will use this constructor most of the time 
    public Samurai() { 
     _weapon = ??? // get an instance of the default weapon somehow 
    } 

    // consumers will use this constructor if they want to explicitly 
    // configure dependencies for this instance 
    public Samurai(IWeapon weapon) { 
     _weapon = weapon; 
    } 
} 

Mi primera solución sería utilizar el patrón de servicio de localización.

El código se vería así:

... 
public Samurai() { 
    _weapon = ServiceLocator.Instance.Get<IWeapon>(); 
} 
... 

Tengo un problema con esto, sin embargo. El localizador de servicios ha sido marcado como un antipatrón (link) y estoy completamente de acuerdo con estos argumentos. Por otro lado, Martin Fowler aboga por el uso del patrón de localizador de servicios exactamente en esta situación (proyectos de biblioteca) (link). Quiero ser cuidadoso y eliminar la posible necesidad de reescribir la biblioteca después de que se muestre que el localizador de servicios realmente fue una mala idea.

Por lo tanto, en conclusión, ¿cree que el localizador de servicios está bien en este escenario? ¿Debo resolver mi problema de una manera completamente diferente? Cualquier idea es bienvenida ...

Respuesta

4

Si quiere hacer la vida más fácil para los usuarios que no utilizan un contenedor DI, puede proporcionar las instancias predeterminadas a través de una clase dedicada Defaults que cuenta con métodos como éste:

public virtual Samurai CreateDefaultSamurai() 
{ 
    return new Samurai(CreateDefaultWeapon()); 
} 

public virtual IWeapon CreateDefaultWeapon() 
{ 
    return new Shuriken(); 
} 

De esta manera usted no necesita contaminar las clases con constructores predeterminados, y sus usuarios no corren el riesgo de usar esos constructores predeterminados de forma involuntaria.

+2

Es importante mantener una clase ID amigable. +1 –

1

Hay una alternativa, que es la inyección de un proveedor específico, digamos que un WeaponProvider en su caso en su clase por lo que puede hacer la búsqueda por usted:

public interface IWeaponProvider 
{ 
    IWeapon GetWeapon(); 
} 

public class Samurai 
{ 
    private readonly IWeapon _weapon; 

    public Samurai(IWeaponProvider provider) 
    { 
     _weapon = provider.GetWeapon(); 
    } 
} 

Ahora puede proporcionar un proveedor local predeterminado para un arma:

public class DefaultWeaponProvider : IWeaponProvider 
{ 
    public IWeapon GetWeapon() 
    { 
     return new Sword(); 
    } 
} 

Y puesto que este es un defecto local (en contraposición a uno de un montaje distinto, por lo que no es una "inyección bastardo"), puede utilizarlo como parte de tu clase Samurai también:

public class Samurai 
{ 
    private readonly IWeapon _weapon; 

    public Samurai() : this(new DefaultWeaponProvider()) 
    { 
    } 

    public Samurai(IWeaponProvider provider) 
    { 
     _weapon = provider.GetWeapon(); 
    } 
} 
+0

Esto parece una alternativa realmente elegante.Me gustaría hacer una pregunta adicional más. – Tomas

+0

Ahora tengo que codificar el enlace de IWeaponProvider a DefaultWeaponProvider en el constructor de la clase Samurai. ¿Ves una forma de cómo este enlace podría refactorizarse a algún módulo de composición? Al igual que con los contenedores IOC, tiene un archivo de configuración único en el que define algo así como "Bind (). To ();" (- ejemplo de Ninject). Incluso si no hay forma de hacerlo, aceptaré tu respuesta. Solo quería aclarar ... Gracias – Tomas

+0

solo para aclarar: por "convención sobre la configuración" quiere decir * todavía * quiere configurar cuáles serán estos valores predeterminados? Pero sí, podrías implementar tu 'DefaultWeaponProvider' para vincular su' IWeapon' a algo realmente, es decir, basado en un archivo de configuración o un uso interno de un contenedor IoC, pero tu clase 'Samurai' no tendrá ninguna de esas dependencias – BrokenGlass

1

He utilizado el siguiente enfoque en mi proyecto C#. El objetivo era lograr la inyección de dependencia (para pruebas unitarias/simuladas) sin afectar la implementación del código para un "caso de uso normal" (es decir, tener una gran cantidad de nuevos() que se conectan en cascada a través del flujo de ejecución).

public sealed class QueueProcessor : IQueueProcessor 
{ 
    private IVbfInventory vbfInventory; 
    private IVbfRetryList vbfRetryList; 

    public QueueProcessor(IVbfInventory vbfInventory = null, IVbfRetryList vbfRetryList = null) 
    { 
     this.vbfInventory = vbfInventory ?? new VbfInventory(); 
     this.vbfRetryList = vbfRetryList ?? new VbfRetryList(); 
    } 
} 

Esto permite DI, pero también significa que cualquier consumidor no tiene que preocuparse por lo que la "instancia predeterminada fluya" debe ser.

Cuestiones relacionadas