c# fluent

c# - creando API que es fluida



fluent (9)

¿Cómo se hace para crear una API que sea fluida en la naturaleza?

¿Está esto utilizando principalmente métodos de extensión?



Escribir una API fluida es complicado, por eso escribí Diezel que es un generador de API Fluent para Java. Genera la API con interfaces (o curso) para:

  1. controlar el flujo de llamadas
  2. capturar tipos genéricos (como uno guice)

Genera también implementaciones.

Es un plugin de maven.


Esta es una pregunta muy antigua, y esta respuesta probablemente debería ser un comentario en lugar de una respuesta, pero creo que es un tema sobre el que vale la pena seguir hablando, y esta respuesta es demasiado larga para ser un comentario.

El pensamiento original relativo a la "fluidez" parece haber sido básicamente sobre la adición de poder y flexibilidad (encadenamiento de métodos, etc.) a los objetos, al tiempo que hace que el código sea un poco más autoexplicativo.

Por ejemplo

Company a = new Company("Calamaz Holding Corp"); Person p = new Person("Clapper", 113, 24, "Frank"); Company c = new Company(a, ''Floridex'', p, 1973);

es menos "fluido" que

Company c = new Company().Set .Name("Floridex"); .Manager( new Person().Set.FirstName("Frank").LastName("Clapper").Awards(24) ) .YearFounded(1973) .ParentCompany( new Company().Set.Name("Calamaz Holding Corp") ) ;

Pero para mí, lo último no es realmente más poderoso, flexible o autoexplicativo que

Company c = new Company(){ Name = "Floridex", Manager = new Person(){ FirstName="Frank", LastName="Clapper", Awards=24 }, YearFounded = 1973, ParentCompany = new Company(){ Name="Calamaz Holding Corp." } };

... de hecho, llamaría a esta última versión más fácil de crear, leer y mantener que la anterior, y diría que también requiere mucho menos equipaje detrás de escena. Lo que para mí es importante, por (al menos) dos razones:

1 - El costo asociado con la creación y el mantenimiento de capas de objetos (sin importar quién lo haga) es tan real, relevante e importante como el costo asociado con la creación y el mantenimiento del código que los consume.

2 - El aumento de código incrustado en capas de objetos crea tantos problemas (si no más) como el aumento de código en el código que consume esos objetos.

El uso de la última versión significa que puede agregar una propiedad (potencialmente útil) a la clase de la Compañía simplemente agregando una línea de código muy simple.

Eso no quiere decir que siento que no hay lugar para el encadenamiento de métodos. Me gusta mucho poder hacer cosas como (en JavaScript)

var _this = this; Ajax.Call({ url: ''/service/getproduct'', parameters: {productId: productId}, ) .Done( function(product){ _this.showProduct(product); } ) .Fail( function(error){ _this.presentError(error); } );

..where (en el caso hipotético que estoy imaginando) Done y Fail fueron adiciones al objeto Ajax original, y se pudieron agregar sin cambiar el código del objeto Ajax original o cualquiera del código existente que hizo uso del Objeto Ajax original, y sin crear cosas únicas que fueran excepciones a la organización general del código.

Así que definitivamente he encontrado valor al hacer que un subconjunto de las funciones de un objeto devuelva el objeto ''this''. De hecho, cada vez que tengo una función que de otro modo se devolvería vacía, considero que la devuelva.

Pero todavía no he encontrado un valor significativo en la adición de "interfaces fluidas" (.eg "Conjunto") a un objeto, aunque en teoría parece que podría haber una especie de organización de código similar al espacio de nombres que podría surgir de la práctica de hacer eso, lo que podría valer la pena. ("Set" puede no ser particularmente valioso, pero "Command", "Query" y "Transfer" podrían ayudar si organiza las cosas y facilita y minimiza el impacto de las adiciones y cambios.) Uno de los beneficios potenciales de esta práctica Dependiendo de cómo se hizo, podría ser una mejora en el nivel típico de cuidado y atención a los niveles de protección de un codificador, cuya falta ciertamente ha causado un gran dolor en los volúmenes.


KISS: Mantenlo simple, estúpido.

El diseño fluido se trata de un principio de diseño estético utilizado en todo el API. Tu metodología que utilizas en tu API puede cambiar ligeramente, pero generalmente es mejor mantenerte consistente.

Aunque piense que ''todos pueden usar esta API, porque usa todos los diferentes tipos de metodologías''. La verdad es que el usuario empezaría a sentirse perdido porque está cambiando constantemente la estructura de estructura / datos de la API a un nuevo principio de diseño o convención de nomenclatura.

Si desea cambiar a la mitad de un principio de diseño diferente, por ejemplo, ... Convertir de códigos de error a manejo de excepciones debido a un alto poder de mando. Sería una locura y normalmente en la cola un montón de dolor. Es mejor mantener el curso y agregar la funcionalidad que sus clientes pueden usar y vender que hacer que vuelvan a escribir y que vuelvan a descubrir todos sus problemas.

Siguiendo con lo anterior, puede ver que hay más en el trabajo de escribir una API Fluent que la de conocer a los ojos. Hay que tomar decisiones psicológicas y estéticas antes de comenzar a escribir una, e incluso entonces el sentimiento, la necesidad y el deseo de ajustarse a las demandas de los clientes y mantener la coherencia es la más difícil de todas.


MrBlah,

Aunque puede escribir métodos de extensión para escribir una interfaz fluida, un mejor enfoque es usar el patrón de construcción. Estoy en el mismo barco que tú y estoy tratando de descubrir algunas características avanzadas de las interfaces fluidas.

A continuación verá un código de ejemplo que creé en otro hilo.

public class Coffee { private bool _cream; private int _ounces; public static Coffee Make { get { return new Coffee(); } } public Coffee WithCream() { _cream = true; return this; } public Coffee WithOuncesToServe(int ounces) { _ounces = ounces; return this; } } var myMorningCoffee = Coffee.Make.WithCream().WithOuncesToServe(16);


No y sí. Los conceptos básicos son una buena interfaz o interfaces para los tipos que desea que se comporten con fluidez. Las bibliotecas con métodos de extensión pueden extender este comportamiento y devolver la interfaz. Los métodos de extensión dan a otros la posibilidad de extender su API fluida con más métodos.

Un buen diseño fluido puede ser difícil y requiere un período de prueba y error bastante largo para perfeccionar totalmente los bloques de construcción básicos. Solo una API fluida para la configuración o configuración no es tan difícil.

Aprender a construir una API fluida hace una mirando las API existentes. Compare el FluentNHibernate con las API .NET fluidas o las interfaces fluidas ICriteria. Muchas API de configuración también están diseñadas "con fluidez".


Si bien muchas personas mencionan a Martin Fowler como un destacado exponente en la discusión de la API fluida, sus afirmaciones iniciales sobre el diseño realmente evolucionan alrededor de un patrón de constructor fluido o un encadenamiento de métodos . Las API fluidas pueden evolucionar aún más en lenguajes específicos reales de dominio interno . Un artículo que explica cómo una notación BNF de una gramática se puede transformar manualmente en una "API fluida" se puede ver aquí:

http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/

Transforma esta gramática:

En esta API de Java:

// Initial interface, entry point of the DSL interface Start { End singleWord(); End parameterisedWord(String parameter); Intermediate1 word1(); Intermediate2 word2(); Intermediate3 word3(); } // Terminating interface, might also contain methods like execute(); interface End { void end(); } // Intermediate DSL "step" extending the interface that is returned // by optionalWord(), to make that method "optional" interface Intermediate1 extends End { End optionalWord(); } // Intermediate DSL "step" providing several choices (similar to Start) interface Intermediate2 { End wordChoiceA(); End wordChoiceB(); } // Intermediate interface returning itself on word3(), in order to allow // for repetitions. Repetitions can be ended any time because this // interface extends End interface Intermediate3 extends End { Intermediate3 word3(); }

Java y C # son algo similares, el ejemplo también se traduce a su caso de uso. La técnica anterior ha sido muy utilizada en jOOQ , un lenguaje específico de dominio / API interno fluido que modela el lenguaje SQL en Java


Este artículo lo explica mucho mejor de lo que nunca pude.

EDITAR, no puede exprimir esto en un comentario ...

Hay dos lados para las interfaces, la implementación y el uso. Hay más trabajo por hacer en el lado de la creación, estoy de acuerdo con eso, sin embargo, los principales beneficios se pueden encontrar en el lado del uso de las cosas De hecho, para mí, la principal ventaja de las interfaces fluidas es una API más natural, más fácil de recordar y usar y, por qué no, más agradable estéticamente. Y quizás, ¿el esfuerzo de tener que comprimir una API de forma fluida puede llevar a una API mejor pensada?

Como dice Martin Fowler en el artículo original sobre interfaces fluidas :

Probablemente, lo más importante a tener en cuenta sobre este estilo es que la intención es hacer algo en la línea de un lenguaje interno específico del dominio. De hecho, esta es la razón por la que elegimos el término ''fluido'' para describirlo, de muchas maneras los dos términos son sinónimos. La API está diseñada principalmente para ser legible y para fluir. El precio de esta fluidez es más esfuerzo, tanto en el pensamiento como en la propia construcción de API. La API simple de los métodos de construcción, configuración y adición es mucho más fácil de escribir. Crear una buena API fluida requiere un poco de reflexión.

Como en la mayoría de los casos, las API se crean una vez y se utilizan una y otra vez, el esfuerzo adicional puede valer la pena.

Y verbosa? Estoy a favor de la verbosidad si sirve para la legibilidad de un programa.


¿Qué es una API fluida?

Wikipedia los define aquí en.wikipedia.org/wiki/Fluent_interface

¿Por qué no utilizar una interfaz fluida?

Yo sugeriría no implementar una interfaz fluida tradicional, ya que aumenta la cantidad de código que necesita escribir, complica su código y solo agrega repetitivo innecesario.

Otra opción, no hacer nada!

No implementes nada. No proporcione constructores "fáciles" para establecer propiedades y no proporcione una interfaz inteligente para ayudar a su cliente. Permitir que el cliente establezca las propiedades como lo harían normalmente. En .Net C # o VB, esto podría ser tan simple como usar inicializadores de objetos .

Car myCar = new Car { Name = "Chevrolet Corvette", Color = Color.Yellow };

Por lo tanto, no necesita crear ninguna interfaz inteligente en su código, y esto es muy legible.

Si tiene conjuntos de propiedades muy complejos que deben establecerse, o establecerse en un cierto orden, entonces use un objeto de configuración separado y páselo a la clase a través de una propiedad separada.

CarConfig conf = new CarConfig { Color = Color.Yellow, Fabric = Fabric.Leather }; Car myCar = new Car { Config = conf };