6

He estado experimentando con el uso de delegados con nombre en lugar de interfaces de método único. Esto tiene algunas ventajas para el tamaño del código, ya que podemos pasar de (algunos saltos de línea retirados a fin de no exagerar el caso):¿Existe una manera simple de registrar cierres estáticos con Castle Windsor?

public interface IProductSource 
{ 
    IEnumerable<Product> GetProducts(); 
} 
public class DataContextProductSource : IProductSource 
{ 
    private readonly DataContext _DataContext; 
    public DataContextProductSource(DataContext dataContext) 
    { 
     if (dataContext == null) throw new ArgumentNullException("dataContext"); 
     _DataContext = dataContext; 
    } 
    public IEnumerable<Product> GetProducts() 
    { 
     return _DataContext.Products.AsEnumerable(); 
    } 
} 

a:

public delegate IEnumerable<Product> DGetProducts(); 
public static class DataContextFunctions 
{ 
    public DGetProducts GetProducts(DataContext dataContext) 
    { 
     if (dataContext == null) throw new ArgumentNullException("dataContext"); 
     return() => dataContext.Products.AsEnumerable(); 
    } 
} 

esto está teniendo básicamente la ventaja del hecho que una vez que avanzas lo suficiente con la inyección de dependencia, muchas clases se convierten en poco más que cierres. Esas clases se pueden reemplazar con funciones que devuelven lambdas. Conjuntos enteros de funciones relacionadas (que no necesitan encapsular ningún estado mutable, pero que se hubieran expresado usando clases en inyección de dependencia "estándar"), se pueden enrollar en una clase estática (o "módulo" en el lenguaje VB) .

Esto está muy bien, pero tengo problemas para encontrar la mejor manera de registrar estos métodos estáticos con Castle Windsor. Métodos sin dependencias son fáciles:

Component.For<DGetIntegers>().Instance(Integers.GetOneToTen) 

Pero nuestros DataContextFunctions.GetProducts desde arriba tiene algunas dependencias. La mejor manera que he encontrado para registrar este es:

Component.For<DGetProducts>().UsingFactoryMethod(
    kernel => DataContextFunctions.GetProducts(kernel.Resolve<DataContext>()) 

Esto puede llegar a ser muy detallado, y, obviamente, tener que pedir el kernel directamente para cada tipo de dependencia se pierde el punto un poco. Me parece que toda la información estática que un contenedor debería necesitar está disponible, por lo que debería ser posible hacerlo.

La pregunta es, ¿Castle Windsor (o cualquier otro contenedor) tiene una forma simple de hacer esto que me he perdido, o hay problemas técnicos que surgen, o es demasiado nicho como un caso de uso para haber sido ¿incluido?

Respuesta

4

Eso es un enfoque interesante. Creo que podrías hacer que esto funcione con bastante facilidad con un activador personalizado.

+0

Gracias, Krzysztof. Voy a intentarlo en algún momento e informar los resultados. ¿Algún enlace para un buen lugar para comenzar? – ninjeff

+0

@ninjeff si está en 2.5.x, la documentación debe comenzar docs.castleproject.org/(S(hucszcu5ilznbv45fvrim355))/... Si tiene 3.0 beta, ha habido algunos cambios en la API, aunque los conceptos siguen siendo los mismo. Avíseme si el doco es menos que perfecto –

+0

Pude hacerlo funcionar. Publicaré un enlace y una descripción con mi propia respuesta en una hora (los usuarios de bajo índice de karma como yo no podemos responder nuestras propias preguntas en ocho horas). – ninjeff

10

Respuesta corta

Usted está tratando de hacer un DI de contenedores (Castillo de Windsor) realizan la función composición, pero en realidad es dirigido a objeto composición. Eso solo te dará mucha fricción. Supongo que obtendrás la misma experiencia con otros contenedores.

Los Contenedores DI están diseñados alrededor de conceptos orientados a objetos, particularmente SOLIDOS. Funcionan muy bien con estos principios porque fueron diseñados con la comprensión inherente de cosas tales como la inyección de constructor y el auto-cableado.

No hay nada de malo en un enfoque más funcional, pero todavía no he visto un contenedor DI construido alrededor de la composición de funciones en lugar de la composición de objetos.

Respuesta larga

El uso de los delegados como un principio general para DI tiende a ser problemático en lenguas tipos estáticos (por lo menos en .NET) por un par de razones. Conceptualmente, no hay nada de malo en este enfoque desde un delegate can be considered as an anonymous Role Interface. Sin embargo, tiende a ser difícil de manejar debido a la ambigüedad del tipo.

El enfoque más común que normalmente veo es utilizar delegados incorporadas de la BCL, tales como Func<T>, Action<T> y así sucesivamente.Sin embargo, es posible que tenga muchos consumidores diferentes que confían en Func<string>, en cuyo caso las cosas se vuelven ambiguas: el hecho de que un consumidor requiera un Func<string> no significa que requiera que el delegado desempeñe el mismo rol. Aunque es mecánicamente posible utilizar DI con delegados, lo que sucede es que los delegados ocultan los roles de aplicación. Los roles desaparecen, dejando solo mecánica.

A continuación, puede, como se sugiere en el PO definir delegados personalizados para cada función, según lo sugerido por este delegado:

public delegate IEnumerable<Product> DGetProducts(); 

Sin embargo, si se toma esa ruta, a continuación, no se gana nada. Un "delegado de roles" aún debe definirse para cada función. Esto contrasta con la definición de una interfaz similar y debe quedar claro que el único ahorro es un par de soportes en ángulo y una definición método explícito:

public interface IProductSource { IEnumerable<Product> GetProducts(); } 

Eso no es un montón de gastos generales (si lo hay).

También es posible que desee echar un vistazo a esta discusión: http://thorstenlorenz.wordpress.com/2011/07/23/dependency-injection-is-dead-long-live-verbs/

+0

Gracias por su respuesta, Mark. En realidad, no estoy tratando de ir a un enfoque completamente funcional, sino a experimentar con un enfoque híbrido. Tratando de sentir las ventajas y desventajas de usar funciones y objetos para varios conceptos, y ver qué tan bien interactúan. – ninjeff

+0

Con respecto a la sobrecarga de las interfaces, se trata más de la longitud de las clases de implementación que de las interfaces mismas. – ninjeff

1

Basado en los indicadores de Krzysztof, pude escribir un activador personalizado para manejar esto. La fuente está en github.

Registro delegados estáticos como este (siguiendo el ejemplo de la pregunta):

container.Register(Component. 
    For<DGetProducts>(). 
    ImplementedBy(typeof(DataContextFunctions)). 
    Named("GetProducts"). 
    Activator<StaticDelegateActivator>()); 

El código es en gran parte una reescritura de DefaultComponentActivator. También necesitaba copiar y pegar DependencyTrackingScope del código fuente de Windsor, ya que es interno.

La única "prueba" en el proyecto de Pruebas solo cubre un caso de uso único. No sospecho que funcionará para escenarios más avanzados como los proxies.

Acepté la respuesta de Krzysztof, ya que su guía condujo a la solución.

Cuestiones relacionadas