2011-03-23 5 views
25

Cualquier lugar donde necesite un valor de tiempo de ejecución para construir una dependencia particular, Abstract Factory es la solución.IoC Factory: Pros y contras para la interfaz frente a los delegados

Mi pregunta es: ¿Por qué muchas fuentes prefieren FactoryInterface sobre FactoryDelegate para implementar este patrón? ¿Cuáles son los pros y contras para ambas soluciones?

Aquí es un ejemplo para entender lo que quiero decir

Si usted tiene un servicio que necesita un repositorio con un cierto contexto a continuación, el constructor Servicio necesita una fábrica para crear o acceder a su repositorio.

La solución común para esto es crear un RepositoryFactoryInterface de esta manera.

public IRepositoryFactory { 
    IRepository Create(ContextInformation context); 
} 

public class MyService { 
    private IRepositoryFactory repositoryFactory; 
    public MyService(IRepositoryFactory repositoryFactory) 
    { 
     this.repositoryFactory = repositoryFactory: 
    } 

    public void DoSomeService() 
    { 
     ContextInformation context = ....; 

     IRepository repository = this.repositoryFactory.Create(context); 

     repository.Load(...); 
     ... 
     repository.Save(...); 
    } 
} 

también es necesario para implementar la interfaz IRepositoryFactory alguna manera

public MyEf4RepositoryFactory : IRepositoryFactory 
{ 
    IRepository Create(ContextInformation context) 
    { 
     return new MyEf4Repository(context); 
    } 
} 

... y utilizarlo en la aplicación

public void main() 
{ 
    IRepositoryFactory repoFactory = new MyEf4RepositoryFactory(); 
    IService service = new MyService(repoFactory); 

    service.DoSomeService(); 
} 

End ----- solución de la corriente principal - ----

En lugar de RepositoryFactoryInterface puede hacer lo mismo con factorydelegate que requiere menos codificación como esta.

public class MyService { 
    private Func<ContextInformation, IRepository> repositoryFactory; 
    public MyService(Func<ContextInformation, IRepository> repositoryFactory) 
    { 
     this.repositoryFactory = repositoryFactory: 
    } 

    public void DoSomeService() 
    { 
     ContextInformation context = ....; 

     IRepository repository = this.repositoryFactory(context); 

     repository.Load(...); 
     ... 
     repository.Save(...); 
    } 
} 

... y utilizarlo en la aplicación

public void main() 
{ 
    IService service = new MyService(context => new MyEf4Repository(context)); 

    service.DoSomeService(); 
} 

En mi opinión, el factorydelegate context => new MyEf4Repository(context) es mucho más compacto que declarar e implementar una interfaz IRepositoryFactory y MyEf4RepositoryFactory.

Debe haber una razón para esto y quiero saber por qué.

Aquí es una fuente de ejemplo que utiliza la interfaz de aproach: answer to is-there-a-pattern-for-initializing-objects-created-via-a-di-container

[Actualización] 15 meses después de hacer esta pregunta y tener más experiencia con los universers java he cambiado de opinión: Ahora prefiero las interfaces más delegados. Pero no puedo decir por qué. Es solo un sentimiento. Tal vez porque estoy más acostumbrado a eso?

+2

El enfoque FactoryDelegate es la forma funcional de hacer las cosas. – George

Respuesta

4

Personalmente, siempre he usado la solución convencional, simplemente porque no pensé en usar un delegado.

Después de pensarlo, me enfrenté al problema de la separación de preocupaciones. Estoy usando Ninject, y yo no quería que mi módulo de unión a este aspecto (imaginar el repositoy tener algunas dependencias de sí mismo):

class IoCModule : NinjectModule 
{ 
    public override Load() 
    { 
     Bind<Func<Context, IRepository>>() 
      .ToConstant(context => new MyEf4Repository(context, Kernel.Get<IRepositoryDependency1>, Kernel.Get<IRepositoryDependency2>)); 
    } 
} 

Eso no se puede leer en absoluto. Así que todavía utilicé fábricas abstractas completamente tipadas para Separación de Preocupación y legibilidad.

Ahora uso el FuncModule descrito en la pregunta this (a la AutoFac). Entonces puedo hacer esto:

class IoCModule : NinjectModule 
{ 
    public override Load() 
    { 
     Bind<IRepository>().To<MyEf4Repository>(); 
     Bind<IRepositoryDependency1>().To<...>(); 
     Bind<IRepositoryDependency2>().To<...>(); 
    } 
} 

y dejar que ninject calcule las dependencias por mí. Como puede ver, es más legible que usar el método descrito anteriormente y tener que vincular las fábricas para cada dependencia. Esta es la forma en que hice la transición de la solución principal a la solución de delegado.

Para responder a su pregunta. La razón por la que utilicé la solución convencional fue porque al principio no sabía cómo hacerlo de otra manera (esto es en parte causado por la mayoría de los blogs que escriben completamente las fábricas abstractas, ¿pueden ver el círculo?).

+1

+1 para los delegados que no son tan fáciles de tratar para IoC-Container-Frameworks – k3b

1

El objetivo de usar una fábrica abstracta sobre una fábrica simple es agrupar fábricas individuales.

En su caso, si alguna vez necesita su delegado para producir otra cosa que un IRepository que estaría en problemas ..

+0

+1 para * agrupar fábricas individuales *. –

+0

Si no necesita agrupar fábricas individuales, sino solo desea suministrar parámetros de tiempo de ejecución a sus dependencias, entonces _grupo de fábricas individuales_ simplemente no es lo que desea, y por lo tanto no es el objetivo de utilizar la fábrica abstracta. Tal vez en este contexto estamos usando el término "Fábrica Abstracta" mal? – dvdvorle

+0

@MrHappy, dijo que el punto de usar _abstract factory en una fábrica simple_. Esto es importante ya que una fábrica simple no necesariamente tiene una relación con otras fábricas, mientras que una fábrica _abstract_ sí lo hace, ya que es una base para una clasificación de fábricas. – rossipedia

10

En cualquier lugar donde se necesite un valor de tiempo de ejecución para la construcción de una dependencia en particular, Resumen La fábrica es la solución.

Discutiría en contra de esto. Las dependencias no se deben construir utilizando datos de tiempo de ejecución, como explained here.En resumen, el artículo afirma:

No inyectar datos de tiempo de ejecución de aplicaciones en componentes durante la construcción; causa ambigüedad, complica la raíz de la composición con una responsabilidad adicional y hace que sea extraordinariamente difícil verificar la exactitud de su configuración de DI. En su lugar, permita que los datos de tiempo de ejecución fluyan a través de las llamadas a métodos de gráficos de objetos construidos.

Cuando dejamos que los datos de tiempo de ejecución "fluyen a través de las llamadas a métodos de gráficos de objetos construidos" en vez, verá la utilidad de las fábricas abstractas declinar. Todavía se pueden usar cuando los datos de tiempo de ejecución se utilizan para elegir entre varias dependencias (en comparación con la inyección de datos de tiempo de ejecución en una dependencia), pero incluso entonces, Abstract Factories no suele ser la mejor solución, como explained here. En resumen, el artículo afirma:

En general, el uso de una abstracción fábrica no es un diseño que tenga en cuenta sus consumidores. De acuerdo con el Principio de Inyección de Dependencia (DIP), las abstracciones deben ser definidas por sus clientes, y como una fábrica aumenta el número de dependencias de las que un cliente está obligado a depender, la abstracción claramente no se crea a favor del cliente y podemos, por lo tanto, considere esto como una violación del DIP.

En cambio, patrones tales como de la fachada, Compuesto, Mediador y Proxy son generalmente una solución mejor.

Eso no significa que no pueda tener código en su aplicación que produzca dependencias, pero no debe definirse como abstracción utilizada por otros componentes de la aplicación. En su lugar, el comportamiento tipo fábrica se debe encapsular en adaptadores que se definen como parte de su Composition Root.

Cuando sólo tiene estos lógica y dependencias de fábrica como como parte de su raíz Composición, que en realidad no importa si se define una IRepositoryFactory o simplemente utilizar un Func<IRepository> para la construcción de dicha dependencia, ya que el IRepositoryFactory se definiría en la raíz de composición también (ya que la aplicación no tiene ningún negocio en el uso de dicha fábrica).

Dicho esto, en el raro caso de que Abstract Factory sea la abstracción correcta (lo que normalmente ocurrirá cuando se está construyendo un marco reutilizable), el uso de interfaces de fábrica es mucho más revelador que el uso de delegados . Es un poco más detallado, pero mucho más claro cuál es el significado de tal cosa. Un IControllerFactory tiene más la intención de revelar que Func<IController>.

Yo diría que esto es aún más válido para las fábricas que no producen dependencias sino valores de datos. Tomemos por ejemplo el ejemplo de inyectar un Func<DateTime> en un constructor. ¿Qué significa esto realmente y qué valor devuelve? ¿Es intuitivo que devuelve DateTime.Now, o devuelve DateTime.Today, o algo más? En ese caso, sería mucho más claro definir una interfaz ITimeProvider con un método GetCurrentTime().

NOTA: Esta respuesta se actualizó en julio de 2017 para reflejar mis últimas opiniones.

+0

Func es realmente críptico. Pero considero que Func es bastante inequívoco. Tal vez para el primer caso todavía debe crear fábricas completas, pero para los otros casos simplemente use Func <...>? – dvdvorle

+0

@Mr Happy: Prefiero ser un poco más detallado, pero creo que no estamos de acuerdo con esto :-) – Steven

+2

Otro punto de feliz término medio: puede definir tipos de delegados personalizados (principalmente para el beneficio de un nombre descriptivo) en muchas circunstancias. –

3

Creo que llamar "fábrica" ​​a un delegado no debería ser correcto.

Factory tiene la responsabilidad de instanciar algún tipo soportado, como alguna implementación de repositorio.

Hacer citas con delegados no tiene sentido porque está definiendo cómo se crea una instancia de su repositorio cada vez que desea crear una instancia de su servicio.

La pregunta para mí es por qué debería usar un delegado si puede implementar un parámetro genérico TRepository que tenga restricciones "nuevas" y "de clase" y, en tiempo de construcción, crear instancias del repositorio dentro de su servicio.

Es solo una opinión, pero parece que desea un acceso directo en lugar de una solución mejor, mejor diseñada y óptima.

En resumen:

  • fábrica durante un delegado permite la inversión de control incluso para la propia fábrica.

  • Delegate over factory no tiene ninguna ventaja ya que incluso puede eliminar la necesidad de un delegado - quizás, entonces, es inútil o redundante -.

  • Delegado "fábrica" ​​no será una fábrica porque se implementarán N formas de crear una instancia de repositorio, rompiendo la necesidad y la ventaja de la fábrica.

  • No hay necesidad de crear manualmente instancias de algún repositorio en el código del consumidor. Simplemente use un parámetro genérico para que se pueda proporcionar un tipo de repositorio para crear una instancia apropiada, gracias a la inversión del control.

3

Me gusta y uso la solución de delegado, ya que es más conciso. Para evitar el problema de legibilidad con los contenedores de IoC que Mr. Happy mencionó, no use Func. En su lugar, crea tu propio delegado designado.

delegate IRepository RepositoryFactory(ContextInformation context); 

Ahora usted tiene lo mejor de ambos mundos: la concisión de los delegados y la legibilidad de los contenedores COI.

+0

+1 como un compromiso simple para obtener información que se vuelve más prolija. ¿Qué hay de nombrar al delegado 'RepositoryDelegate' en lugar de' IRepository'. De otra manera, creo que es un parámetro de interfaz. – k3b

+0

IRespository es el valor de retorno de la función, y es una interfaz como usted adivinó. El delegado se llama RespositoryFactory, ya que también es una fábrica y tiene más significado que RepositoryDelegate. – enl8enmentnow

Cuestiones relacionadas