2012-04-25 16 views
11

Resumeninyección de dependencia y el desarrollo de la productividad

Durante los últimos meses he estado programando un peso ligero, C# motor de juego basado en la abstracción activos y de entidad/sistema de componentes/scripts. La idea general es facilitar el proceso de desarrollo del juego en XNA, SlimDX y demás, proporcionando una arquitectura similar a la del motor de Unity.

Diseño desafía

Como la mayoría de los desarrolladores de juegos saben, hay una gran cantidad de diferentes servicios que necesita acceder a través de su código. Muchos desarrolladores recurren al uso de instancias globales estáticas de, p. un gestor de Render (o un compositor), una escena, un dispositivo de gráficos (DX), un registrador, un estado de entrada, una ventana gráfica, etc. Hay algunos enfoques alternativos a las instancias/singletons estáticos globales. Una es dar a cada clase una instancia de las clases a las que necesita acceso, ya sea mediante un constructor o una inyección de dependencia de propiedad (PI), otra es usar un localizador de servicio global, como ObjectFactory de StructureMap donde el localizador de servicio generalmente se configura como un contenedor IoC.

inyección de dependencias

yo decidimos ir por el camino DI por muchas razones. La más obvia es la capacidad de prueba, al programar contra interfaces y tener todas las dependencias de cada clase proporcionadas a través de un constructor, esas clases se prueban fácilmente ya que el contenedor de prueba puede instanciar los servicios requeridos, o los simulacros de ellos, y alimentar a cada clase para ser probada. Otra razón para hacer DI/IoC fue, lo creas o no, para aumentar la legibilidad del código. Ya no hay un proceso de inicialización enorme de crear instancias de todos los servicios diferentes y crear instancias manualmente de clases con referencias a los servicios requeridos. La configuración del Kernel (NInject)/Registry (StructureMap) proporciona de manera conveniente un único punto de configuración para el motor/juego, donde las implementaciones del servicio se seleccionan y configuran.

Mis problemas

  • menudo me siento como que estoy creando interfaces para el bien de interfaces
  • Mi productividad ha bajado drásticamente desde que todo lo que hago es preocuparse acerca de cómo hacer las cosas de la DI-manera, en lugar de la manera global estática rápida y simple.
  • En algunos casos, p. al crear instancias de Entidades nuevas en tiempo de ejecución, se necesita acceso al contenedor/ker del IoC para crear la instancia. Esto crea una dependencia en el propio contenedor IoC (ObjectFactory en SM, una instancia del kernel en Ninject), lo que realmente va en contra de la razón para usar uno en primer lugar. ¿Como puede ésto ser resuelto? Las fábricas abstractas vienen a la mente, pero eso complica aún más el código.
  • Dependiendo de los requisitos del servicio, los constructores de algunas clases pueden llegar a ser muy grandes, lo que hará que la clase sea completamente inútil en otros contextos donde y si no se usa un IoC.

Básicamente hacer DI/IoC reduce drásticamente mi productividad y en algunos casos complica aún más el código y la arquitectura. Por lo tanto, no estoy seguro de si es un camino que debería seguir, o simplemente renunciar y hacer las cosas a la vieja usanza.No busco una sola respuesta que diga lo que debería o no debería hacer, pero una discusión sobre si el uso de DI vale la pena a largo plazo en lugar de utilizar el modo global estático/único, los posibles pros y contras que he pasado por alto y posibles soluciones a mis problemas enumerados anteriormente, cuando se trata de DI.

Respuesta

19

¿Debería volver a la forma antigua? Mi respuesta en resumen es no. DI tiene numerosos beneficios por todas las razones que mencionaste.

menudo me siento como que estoy creando las interfaces para el motivo de interfaces

Si usted está haciendo esto que podría estar violando la Reused Abstractions Principle (RAP)

Dependiendo de los requisitos de servicio, constructores algunas clases puede obtener muy grande, lo que hará que la clase sea completamente inútil en otros contextos donde y si no se utiliza un IoC.

Si sus clases constructores son demasiado grandes y complejas, esta es la mejor manera de mostrar que usted está violando una muy importante otro principio: Single Reponsibility Principle. En este caso es el momento de extraer y refactorizar su código en diferentes clases, el número de dependencias sugeridas es alrededor de 4.

Para hacer DI no es necesario que tenga una interfaz, DI es la forma en que consigue tus dependencias en tu objeto. Crear interfaces puede ser una forma necesaria para poder sustituir una dependencia con fines de prueba. A menos que el objeto de la dependencia es:

  1. Fácil aislar
  2. no habla a los subsistemas externos (sistema de archivos etc)

puede crear su dependencia como una clase abstracta, o cualquier clase donde los métodos que desea sustituir son virtuales. Sin embargo, las interfaces crean la mejor forma desconectada de una dependencia.

En algunos casos, p. al crear instancias de Entidades nuevas en tiempo de ejecución, uno necesita acceder al contenedor/ker de IoC para crear la instancia. Esto crea una dependencia en el propio contenedor de IoC (ObjectFactory en SM, una instancia del kernel en Ninject), que realmente va con el motivo de usar uno en primer lugar. ¿Cómo puede ser esto resuelto? Las fábricas abstractas vienen a la mente, pero eso solo más complica el código.

En cuanto a la dependencia del contenedor IOC, nunca debería tener una dependencia en las clases de cliente. Y no es necesario.

Para utilizar por primera vez la inyección de dependencia es necesario comprender el concepto de Composition Root. Este es el único lugar donde se debe hacer referencia a su contenedor. En este punto, se construye todo el gráfico de objetos. Una vez que comprenda esto, se dará cuenta de que nunca necesita el contenedor en sus clientes. Como cada cliente acaba de obtener su dependencia inyectada.

También hay muchos otros patrones de creación que puede seguir para hacer la construcción más fácil: decir que quiere construir un objeto con muchas dependencias como este:

new SomeBusinessObject(
    new SomethingChangedNotificationService(new EmailErrorHandler()), 
    new EmailErrorHandler(), 
    new MyDao(new EmailErrorHandler())); 

puede crear una fábrica de cemento que sabe cómo construir este:

public static class SomeBusinessObjectFactory 
{ 
    public static SomeBusinessObject Create() 
    { 
     return new SomeBusinessObject(
      new SomethingChangedNotificationService(new EmailErrorHandler()), 
      new EmailErrorHandler(), 
      new MyDao(new EmailErrorHandler())); 
    } 
} 

y luego usarlo como esto:

SomeBusinessObject bo = SomeBusinessObjectFactory.Create(); 

También puede utilizar pobre sirve di y crear un constructor que no toma ningún argumento en todo:

public SomeBusinessObject() 
{ 
    var errorHandler = new EmailErrorHandler(); 
    var dao = new MyDao(errorHandler); 
    var notificationService = new SomethingChangedNotificationService(errorHandler); 
    Initialize(notificationService, errorHandler, dao); 
} 

protected void Initialize(
    INotificationService notifcationService, 
    IErrorHandler errorHandler, 
    MyDao dao) 
{ 
    this._NotificationService = notifcationService; 
    this._ErrorHandler = errorHandler; 
    this._Dao = dao; 
} 

Entonces sólo parece que se utiliza para trabajar:

SomeBusinessObject bo = new SomeBusinessObject(); 

Usando DI hombre pobre se considera mal cuando las implementaciones predeterminadas se encuentran en bibliotecas externas de terceros, pero menos graves cuando se tiene una buena implementación predeterminada.

Luego, obviamente, están todos los contenedores DI, constructores de Objetos y otros patrones.

Así que todo lo que necesita es pensar en un buen patrón de creación para su objeto. Su objeto en sí no debería importar cómo crear las dependencias, de hecho las hace MÁS complicadas y hace que mezclen 2 tipos de lógica. Por lo tanto, no creo que el uso de DI deba tener pérdida de productividad.

Existen algunos casos especiales en los que su objeto no puede obtener una sola instancia inyectada. Donde la duración es generalmente más corta y se requieren instancias sobre la marcha. En este caso debe inyectarse la fábrica en el objeto como una dependencia:

public interface IDataAccessFactory 
{ 
    TDao Create<TDao>(); 
} 

Como se puede notar esta versión es genérico, ya que puede hacer uso de un contenedor IoC para crear diversos tipos (Tome en cuenta sin embargo el contenedor IoC todavía no es visible para mi cliente).

public class ConcreteDataAccessFactory : IDataAccessFactory 
{ 
    private readonly IocContainer _Container; 

    public ConcreteDataAccessFactory(IocContainer container) 
    { 
     this._Container = container; 
    } 

    public TDao Create<TDao>() 
    { 
     return (TDao)Activator.CreateInstance(typeof(TDao), 
      this._Container.Resolve<Dependency1>(), 
      this._Container.Resolve<Dependency2>()) 
    } 
} 

Aviso Solía ​​activador pesar de que tenía un contenedor COI, esto es importante tener en cuenta que la fábrica necesita para construir una nueva instancia de objeto y no sólo asumir el envase proporcionará una nueva instancia que el objeto puede estar registrado con diferentes tiempos de vida (Singleton, ThreadLocal, etc.). Sin embargo, dependiendo del contenedor que esté utilizando, puede generar estas fábricas para usted. Sin embargo, si está seguro de que el objeto está registrado con una vida útil transitoria, simplemente puede resolverlo.

EDIT: Adición de clase con la dependencia Abstract Factory:

public class SomeOtherBusinessObject 
{ 
    private IDataAccessFactory _DataAccessFactory; 

    public SomeOtherBusinessObject(
     IDataAccessFactory dataAccessFactory, 
     INotificationService notifcationService, 
     IErrorHandler errorHandler) 
    { 
     this._DataAccessFactory = dataAccessFactory; 
    } 

    public void DoSomething() 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      using (var dao = this._DataAccessFactory.Create<MyDao>()) 
      { 
       // work with dao 
       // Console.WriteLine(
       //  "Working with dao: " + dao.GetHashCode().ToString()); 
      } 
     } 
    } 
} 

Básicamente haciendo DI/COI ralentiza drásticamente mi productividad y en algunos casos complica aún más el código y la arquitectura

Marcos Seeman escribió un blog impresionante sobre el tema, y ​​respondió la pregunta: Mi primera reacción a ese tipo de pregunta es: usted dice que el código débilmente acoplado es más difícil de entender. Más difícil que qué?

Loose Coupling and the Big Picture

EDIT: Por último, me gustaría señalar que no todos los objetos y de dependencia o necesidades deben ser inyectada dependencia, en primer lugar considerar si lo que está utilizando es actualmente considerada como una dependencia:

Lo son dependencias?

  • configuración de la aplicación
  • los recursos del sistema (reloj)
  • Bibliotecas
  • Terceros
  • base de datos
  • WCF/Red de Servicios
  • sistemas externos (Archivo/Correo electrónico)

Cualquier de los objetos o colaboradores anteriores pueden estar fuera de su control y causar efectos secundarios fectos y diferencias en el comportamiento y lo hacen difícil de probar. Estos son los momentos para considerar una Abstracción (Clase/Interfaz) y usar DI.

¿Qué no son dependencias, realmente no necesita DI?

  • List<T>
  • MemoryStream
  • Cuerdas/Primitives
  • objetos hoja/del Dto

objetos como el anterior, simplemente se pueden crear instancias donde sea necesario el uso de la palabra clave new. No recomendaría usar DI para objetos tan simples a menos que haya razones específicas. Considere la pregunta si el objeto está bajo su control total y no causa ningún gráfico adicional de objetos o efectos secundarios en el comportamiento (al menos cualquier cosa que quiera cambiar/controlar el comportamiento o la prueba). En este caso, simplemente recógelos.

He publicado muchos enlaces a las publicaciones de Mark Seeman, pero realmente le recomiendo que lea sus publicaciones de libros y blogs.

+0

Excelente respuesta, directamente sobre el tema, ¡gracias! Leí los dos artículos que vinculó, que son una gran fuente de información y en realidad respondieron muchas otras preguntas que tenía en mente. La violación de RAP y SRP se destacan como cosas buenas para tener en cuenta en mi caso. Una última cosa que se destaca de su respuesta, menciona el uso de fábricas abstractas para ocultar un contenedor IoC de su cliente, ¿está bien entonces que una fábrica abstracta dependa de un contenedor IoC? – edvaldig

+1

Gracias, me alegro de que esto te haya ayudado, y yo diría que sí, que la fábrica abstracta es muy aceptable para saber sobre el contenedor, ya que la fábrica opera en la misma capa que la raíz de composición en el 'pegamento' y no contiene lógica de negocios, simplemente gobierna las instancias concretas. Por lo tanto, en un entorno de prueba es posible que ni siquiera use un contenedor Ioc y necesitaría una fábrica diferente porque el tipo concreto sería diferente – Andre

+0

Hola, acabo de agregar otra edición al final para que comprenda qué es una dependencia, y no preocuparse por cualquier gestión de dependencia para esos objetos que son simples y no lo requieren. – Andre

Cuestiones relacionadas