c# dsl fluent-interface api-design

c# - ¿Cuál es el punto de DSL/interfaces fluidas



fluent api (7)

Recientemente estuve viendo un webcast sobre cómo crear un DSL fluido y tengo que admitir que no entiendo las razones por las que uno usaría ese enfoque (al menos para el ejemplo dado).

El webcast presentó una clase de cambio de tamaño de imagen, que le permite especificar una imagen de entrada, cambiar su tamaño y guardarla en un archivo de salida usando la siguiente sintaxis (usando C #):

Sizer sizer = new Sizer(); sizer.FromImage(inputImage) .ToLocation(outputImage) .ReduceByPercent(50) .OutputImageFormat(ImageFormat.Jpeg) .Save();

No entiendo cómo esto es mejor que un método "convencional" que toma algunos parámetros:

sizer.ResizeImage(inputImage, outputImage, 0.5, ImageFormat.Jpeg);

Desde el punto de vista de la usabilidad, esto parece mucho más fácil de usar, ya que claramente le dice qué espera el método como entrada. En contraste, con la interfaz fluida, nada le impide omitir / olvidar un parámetro / método-llamada, por ejemplo:

sizer.ToLocation(outputImage).Save();

Así que a mis preguntas:

1 - ¿Hay alguna manera de mejorar la usabilidad de una interfaz fluida (es decir, decirle al usuario qué se espera que haga)?

2 - ¿Este enfoque de interfaz fluida es solo un reemplazo para los parámetros de métodos nombrados que no existen en C #? Los parámetros con nombre hacen que las interfaces fluidas se vuelvan obsoletas, por ejemplo, algo similar al que el objetivo-C ofrece:

sizer.Resize(from:input, to:output, resizeBy:0.5, ..)

3 - ¿Se utilizan las interfaces con fluidez simplemente porque son populares actualmente?

4 - ¿ O fue solo un mal ejemplo que se eligió para la transmisión por Internet? En ese caso, dígame cuáles son las ventajas de este enfoque, dónde tiene sentido utilizarlo.

BTW: Sé sobre jquery, y veo lo fácil que hace las cosas, así que no estoy buscando comentarios sobre ese u otros ejemplos existentes.

Busco más comentarios (generales) que me ayuden a comprender (por ejemplo) cuándo implementar una interfaz fluida (en lugar de una biblioteca de clases clásica) y qué debo tener en cuenta al implementar una.


2 - ¿Este enfoque de interfaz fluida es solo un reemplazo para los parámetros de métodos nombrados que no existen en C #? Los parámetros con nombre hacen que las interfaces fluidas se vuelvan obsoletas, por ejemplo, algo similar al que el objetivo-C ofrece:

Pues sí y no. La interfaz fluida le da una mayor cantidad de flexibilidad. Algo que no se pudo lograr con params nombrados es:

sizer.FromImage(i) .ReduceByPercent(x) .Pixalize() .ReduceByPercent(x) .OutputImageFormat(ImageFormat.Jpeg) .ToLocation(o) .Save();

El FromImage, ToLocation y OutputImageFormat en la interfaz fluida, me huele un poco. En su lugar, habría hecho algo en este sentido, que creo que es mucho más claro.

new Sizer("bob.jpeg") .ReduceByPercent(x) .Pixalize() .ReduceByPercent(x) .Save("file.jpeg",ImageFormat.Jpeg);

Las interfaces fluidas tienen los mismos problemas que tienen muchas técnicas de programación, pueden ser mal utilizadas, utilizadas en exceso o infrautilizadas. Creo que cuando esta técnica se usa de manera efectiva puede crear un modelo de programación más rico y conciso. Incluso StringBuilder lo soporta.

var sb = new StringBuilder(); sb.AppendLine("Hello") .AppendLine("World");


Además de la sugerencia de @sam-saffron sobre la flexibilidad de una interfaz fluida al agregar una nueva operación:

Si necesitáramos agregar una nueva operación, como Pixalize (), entonces, en el escenario ''método con múltiples parámetros'', esto requeriría que se agregue un nuevo parámetro a la firma del método. Esto puede requerir una modificación de cada invocación de este método a lo largo de la base de código para agregar un valor para este nuevo parámetro (a menos que el idioma en uso permita un parámetro opcional).

Por lo tanto, un beneficio posible de una interfaz fluida es limitar el impacto del cambio futuro.


Considerar:

sizer.ResizeImage(inputImage, outputImage, 0.5, ImageFormat.Jpeg);

¿Qué pasa si usaste nombres de variables menos claros:

sizer.ResizeImage(i, o, x, ImageFormat.Jpeg);

Imagina que has imprimido este código. Es más difícil inferir cuáles son estos argumentos, ya que no tiene acceso a la firma del método.

Con la interfaz fluida, esto es más claro:

sizer.FromImage(i) .ToLocation(o) .ReduceByPercent(x) .OutputImageFormat(ImageFormat.Jpeg) .Save();

Además, el orden de los métodos no es importante. Esto es equivalente:

sizer.FromImage(i) .ReduceByPercent(x) .OutputImageFormat(ImageFormat.Jpeg) .ToLocation(o) .Save();

Además, quizás tenga valores predeterminados para el formato de imagen de salida y la reducción, por lo que esto podría convertirse en:

sizer.FromImage(i) .ToLocation(o) .Save();

Esto requeriría constructores sobrecargados para lograr el mismo efecto.


Debería leer Domain Driven Design por Eric Evans para tener una idea de por qué DSL se considera una buena opción de diseño.

El libro está lleno de buenos ejemplos, consejos de mejores prácticas y patrones de diseño. Muy recomendable.


Es posible usar una variación en una interfaz Fluent para imponer ciertas combinaciones de parámetros opcionales (por ejemplo, requiere que al menos un parámetro de un grupo esté presente, y requiere que si se especifica un determinado parámetro, se debe omitir algún otro parámetro). Por ejemplo, uno podría proporcionar una funcionalidad similar a Enumerable.Range, pero con una sintaxis como IntRange.From (5) .Upto (19) o IntRange.From (5). LessThan (10) .Stepby (2) o IntRange ( 3) .Count (19) .StepBy (17). La aplicación en tiempo de compilación de requisitos de parámetros demasiado complejos puede requerir la definición de un número molesto de estructuras o clases de valor intermedio, pero el enfoque en algunos casos puede resultar útil en casos más simples.


Es una forma de implementar las cosas.

Para los objetos que no hacen nada más que manipular el mismo elemento una y otra vez, no hay nada realmente malo en ello. Considere C ++ Streams: son lo último en esta interfaz. Cada operación devuelve el flujo nuevamente, por lo que puede encadenar otra operación de flujo.

Si estás haciendo LINQ y manipulando un objeto una y otra vez, esto tiene sentido.

Sin embargo, en su diseño, hay que tener cuidado. ¿Cuál debería ser el comportamiento si quiere desviarse a la mitad? (ES DECIR,

var obj1 = object.Shrink(0.50); // obj1 is now 50% of obj2 var obj2 = object.Shrink(0.75); // is ojb2 now 75% of ojb1 or is it 75% of the original?

Si obj2 era el 75% del objeto original, eso significa que estás haciendo una copia completa del objeto cada vez (y tiene sus ventajas en muchos casos, como si estuvieras intentando hacer dos instancias de la misma cosa, pero ligeramente diferente).

Si los métodos simplemente manipulan el objeto original, entonces este tipo de sintaxis es algo falso. Esas son manipulaciones en el objeto en lugar de manipulaciones para crear un objeto cambiado.

No todas las clases funcionan así, ni tiene sentido hacer este tipo de diseño. Por ejemplo, este estilo de diseño tendría poca o ninguna utilidad en el diseño de un controlador de hardware o el núcleo de una aplicación GUI. Mientras el diseño no implique nada más que manipular algunos datos, este patrón no es malo.


Yo diría que las interfaces fluidas están un poco exageradas y creo que usted ha elegido solo uno de estos ejemplos.

Encuentro interfaces fluidas particularmente fuertes cuando estás construyendo un modelo complejo con él. Con modelo me refiero, por ejemplo, a una relación compleja de objetos instanciados. La interfaz fluida es, entonces, una forma de guiar al desarrollador para construir correctamente instancias del modelo semántico. Una interfaz tan fluida es entonces una excelente manera de separar la mecánica y las relaciones de un modelo de la "gramática" que utiliza para construir el modelo, esencialmente protegiendo los detalles del usuario final y reduciendo los verbos disponibles a tal vez solo los relevantes en una Escenario particular.

Tu ejemplo parece un poco como una exageración.

Últimamente he hecho una interfaz fluida en la parte superior del SplitterContainer de Windows Forms. Podría decirse que el modelo semántico de una jerarquía de controles es algo complejo para construir correctamente. Al proporcionar una pequeña API fluida, un desarrollador ahora puede expresar de forma declarativa cómo debería funcionar su SplitterContainer. El uso va como

var s = new SplitBoxSetup(); s.AddVerticalSplit() .PanelOne().PlaceControl(()=> new Label()) .PanelTwo() .AddHorizontalSplit() .PanelOne().PlaceControl(()=> new Label()) .PanelTwo().PlaceControl(()=> new Panel()); form.Controls.Add(s.TopControl);

Ahora he reducido la compleja mecánica de la jerarquía de control a un par de verbos que son relevantes para el tema en cuestión.

Espero que esto ayude