tipos - patron de inyeccion de dependencias c#
Espacio de nombres C#para interfaces en inyección de dependencia (1)
Quiero usar el patrón de Dependency Injection
en C #, y quiero tener las lógicas lo más separadas posible en espacios de nombres.
Pregunta
¿En qué espacio de nombres debería estar la interface
de la clase consumida?
Motivación de la pregunta
Primero hagamos un caso "normal". Una libreta de libros para usar como base para la segunda parte de la explicación. Luego, el caso de la "vida real", que plantea la pregunta.
Librero
Supongamos que el codificador es Alicia y que usa a Alice
como un nombre de alto nivel en los espacios de nombres como proveedor, para evitar cualquier conflicto con otros codificadores. Para este ejemplo, asumiremos que no hay otras Alices en el mundo.
Supongamos que crea 3 espacios de nombres:
-
Alice.Invaders
: un juego que proporcionará compras en la aplicación a través de una tienda. -
Alice.Shop
: una tienda reutilizable para varios juegos. -
Alice.Injector
: un administrador de servicios reutilizable.
Supongamos que el proyecto Shop
tiene una interface
llamada IShopService
que proporciona un método Show()
.
Supongamos que los Invaders
tienen algún tipo de controlador que, en alguna acción del usuario, quiere abrir la tienda.
Supongamos que el controlador de Invaders
obtiene los servicios, como la Shop
, a través de un ServiceManager
.
Alice.Injector
Alice.Injector
es un proyecto independiente que no tiene dependencias, por lo que no usa la palabra clave "using":
namespace Alice.Injector
{
public interface IService
{
// All the services shall implement this interface.
// This is necessary as C# is heavily typed and the
// Get() method below must return a known type.
}
public class ServiceManager
{
static public IService Get( string serviceName )
{
IService result;
// Do the needed stuff here to get the service.
// Alice implements this getter configurable in a text-file
// so if she wants to test the invaders with a mock-shop
// that does not do real-purchases but fake-ones
// she can swap the injected services without changing the
// consumer''s code.
result = DoTheNeededStuff();
return result;
}
}
}
Alice.Shop
Alice.Shop
es también un proyecto independiente que (excepto en el caso de que consuma servicios) no tiene conocimiento de la existencia de un inyector. Solo es una tienda y eso es todo.
Como Alice piensa que quizás Bob vaya a comprar algo mejor algún día, ella prepara su clase para ser inyectada en dependencia, luego de la separación de la Shop
en una interfaz de IShop
y luego la implementación, siguiendo este artículo: https://msdn.microsoft. com / library / hh323705% 28v = vs.100% 29.aspx
Para lograrlo, Alice hará que la tienda sea un servicio compatible con el Alice.ServiceManager
, por lo que Alice decide que la IShop
se renombrará como IShopService
y será una especie de IService
de IService
.
using Alice.Injector
namespace Alice.Shop
{
public interface IShopService : IService
{
public void Show();
}
public class Shop : IShopService
{
public void Show()
{
// Here Alice puts all the code to open the shop up.
}
}
}
Alice.Invaders
Finalmente, Alice codifica el juego. El código de Alice.Invaders
obtiene una Shop
(que toma la forma de un servicio) a través del ServiceManager
por lo que es todo código limpio.
using Alice.Injector
using Alice.Shop
namespace Alice.Invaders
{
public class DefaultController
{
void OnShopClick()
{
IShopService shop = ServiceManager.Get( "Shop" ) as IShopService;
shop.Show();
}
}
}
Hasta aquí, todo esto funciona bien.
Caso de la vida real
Entonces ahora ... Bob (un buen amigo de Alice, como todos ustedes saben, curiosos de que no hablen sobre enviar mensajes encriptados hoy), hace una tienda súper agradable que es incluso más bonita que la que Alice hizo. Bob hace su tienda desde cero.
Entonces, Bob implementa la tienda compatible con el inyector de Alice (ya que Bob también usa Alice.Injector
para inyectar otras cosas en sus proyectos).
using Alice.Injector
namespace Bob.Shop
{
public interface IShopService : IService
{
public void Show();
}
public class Shop : IShopService
{
public void Show()
{
// Here Bob does a brand new shop from scratch.
}
}
}
Entonces ... ¡aquí está la situación extraña!
-
Bob.Shop
Namespace para la interfaz - Si Bob hace su tienda dentro del espacio de nombresBob.Shop
la manera que se muestra arriba, entonces Alice debe editar su código para hacer referencia aBob.Shop
para obtener la interfazIShopService
(fea ella tiene que cambiar la dependencia en el código, ya que se suponía que debía usar Inyectores de Dependencia para deshacerse de cambiar las dependencias en el código). - Sin espacio de nombres para la interfaz : si tanto Alice como Bob configuran el
IShopService
en el espacio de nombres global, también es feo, ya que hay muchas cosas que podrían entrar en conflicto. - Espacio de nombres
Alice.Shop
para la interfaz : si Bob usa el sentido común y dice "Lo que quiero hacer es crear una tienda compatible con la de Alice, entonces debería implementar su interfaz HER", por lo que el código de Bob probablemente será como este :
Código de Bob utilizando Alice.Shop
compatibilidad de espacio de nombres hacia atrás:
namespace Bob.Shop
{
public class Shop : Alice.Shop.IShopService
{
public void Show()
{
// Here Bob does a brand new shop from scratch,
// which borrows Alice''s interface.
}
}
}
En este caso, parece que todo está en su lugar:
- Bob puede crear el
Bob.Shop.Shop
que implemente elAlice.Shop.IShopService
- Alice no necesita cambiar una sola línea de código.
-
Alice.Injector.ServiceManager
puede proporcionar otroIService
al servir elBob.Shop.Shop
.
Problema
Todavía hay una dependencia aquí:
Alice.Invaders
está lanzando el Alice.Injector.IService
a un Alice.Shop.IShopService
para poder llamar al método Show()
. Si no haces ese reparto, no puedes "mostrar la tienda".
Entonces, al final, usted está "dependiendo" de ese elenco y, por lo tanto, "alguien" necesita proporcionarle la definición de la interfaz.
Si la tienda original no fue escrita por Alice, sino por Charlie, sería "feo" tener que descargar y conservar una copia del proyecto Charlie.Shop
para poder utilizar Bob.Shop
.
Asi que...
Preguntas
1) ¿Cuál es el espacio de nombres correcto para que IShopInterface
viva?
2) ¿El proyecto de "reemplazo" proporciona su propia interfaz o toma prestada la original?
3) ¿Debería tal vez dividirse la tienda original en DOS proyectos? (como, por ejemplo, Alice.Shop
y Alice.ShopImplementation
para que Alice.Shop
sea muy delgada y solo contenga las interfaces? Tal vez Alice.Shop
y Alice.Shop.Implementation
como espacio de nombres anidado, pero aún dos bases de código separadas para que pueda descargar ¿Cómo instalar Alice.Shop
sin descargar Alice.Shop.Implementation
?
4) ¿Es tan simple como que Bob incluye una copia del archivo Alice.Shop.IShopInterface
en su proyecto por lo que no se necesitan dependencias? Muy feo: si lo hace y queremos tener las 2 tiendas y enviar a los usuarios a una u otra tienda, eso entraría en conflicto.
Gracias.
Las interfaces, el inyector y la implementación deben estar en diferentes espacios de nombres. Las interfaces deben estar en Alice.Shop.Interfaces
y no debe haber implementaciones en este espacio de nombres. Puede cambiar / ocultar la implementación, pero debe seguir con Interfaces en Inyección de dependencia.
Alice.Invaders está lanzando el Alice.Injector.IService a un Alice.Shop.IShopService para poder llamar al método Show (). Si no haces ese reparto, no puedes "mostrar la tienda".
Su implementación de DefaultController no es buena. Si quiero usarlo, no sé nada sobre qué servicios necesito. Me dice, no necesito nada ahora.
Deberías usar la inyección de constructor.
public class DefaultController
{
private readonly IShopService _shopService;
DefaultController(IShopService shopService)
{
_shopService=shopService;
}
void OnShopClick()
{
_shopService.Show();
}
}
Si necesito el controlador predeterminado, sabría qué servicios necesito con esta implementación. Y no necesitas lanzar.
Editar:
Digamos que Alice tiene una tienda. Ella dice que quiero una sala de lectura que tenga 5 sillas. Pero ella decidirá que las sillas son de madera o cuero (IChairs). Cuando abre la tienda, decide usar sillas de madera (Inject WoodChairs para IChairs).
Entonces Bob compra la tienda de Alice. No puede cambiar la sala de lectura (es difícil tomará tiempo y la sala de lectura está bien). Pero él quiere sillas de cuero, así que usa sillas de cuero (Inject LeatherChairs para IChairs).
Bob debería Alice.Shop.Interfaces
si no puede o no quiere cambiar la sala de lectura.
Pero digamos Me gusta Alice Reading Room mucho. Quiero diseñar una sala de lectura como la de ella. Pero quiero establecer mis reglas para la sala de lectura (adaptador IMyReadingRoom, se obtiene la clase ReadingRoom y no la interfaz y se crean sus propias interfaces).
En resumen : debe pegar interfaces siempre. Puede crear su propia interfaz ( adaptador ) para bibliotecas de terceros. De esta forma, puede extender u ocultar las reglas sin apegarse a la biblioteca de terceros (pero debe seguir con su propia interfaz de todos modos). Debe escribir el adaptador para la implementación de una biblioteca de terceros, no para sus interfaces.
Si Alice.Shop.Interfaces
opción del adaptador, Bob tiene que usar Alice.Shop.Interfaces
para inyectar.