Interfaz Fluída
Escrito por Martin Fowler
Traducido por Rafael Vacas
Revisado por Jorge Ferrer
Actualización: buen seguimiento por Piers Crawley
Hace algunos meses asistí a un taller de Eric Evans, en el que habló acerca de un cierto estilo de interfaz que decidimos llamar interfaz fluida. No es un estilo muy común, pero pensamos que debería conocerse mejor. Probablemente la mejor forma de describirlo es con un ejemplo.
Y el más sencillo es el de Eric en la librería timeAndMoney. La forma usual de crear un intervalo de tiempos sería como sigue:
TimePoint fiveOClock, sixOClock;
...
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
Un usuario de la librería timeAndMoney lo usaría así:
TimeInterval meetingTime = fiveOClock.until(sixOClock);
Continuaré con el típico ejemplo de creación de un pedido para un cliente. El pedido está formado por varias entradas (OrderLines), cada una con un producto y una cantidad. Se puede marcar una entrada como prescindible (skippable), es decir, que podría procesarse el pedido sin ella en lugar de retrasar todo el pedido. Por último, puede marcarse el pedido completo como urgente.
La forma más común que veo para hacerlo es la siguiente:
private void makeNormal(Customer customer) {
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
Básicamente creamos varios objetos y los asociamos. Si no podemos establecer todo en el constructor, entonces es necesario crear variables temporales para poder asociar la información - como cuando añades elementos a colecciones.
Aquí está la misma sentencia en un estilo fluido:
private void makeFluent(Customer customer) {
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
Probablemente lo más importante a tener en cuenta sobre este estilo es su intención de crear un LenguajeEspecificoDelDominio (DSL) interno. De hecho esto es por lo que elegimos el término 'fluido' para describirlo. Principalmente la API está diseñada para ser legible y fluida. El precio de esta fluidez es un mayor esfuerzo, tanto al pensar como al construir la propia API. Una API simple con constructor, métodos set y métodos de suma es mucho más fácil de escribir. Hacerse con una buena API fluida requiere una buena dosis de esfuerzo mental.
De hecho, uno de los problemas de este pequeño ejemplo es el que se me ocurrió desayunando en un café de Calgary. Crear buenas API fluidas lleva un tiempo. Si quieres un ejemplo mucho más elaborado de una API fluida, echa un vistazo a JMock. JMock, como cualquier librería de mocks, necesita crear especificaciones complejas de comportamiento. Se han creado muchas librerías mock en los últimos años, y la de JMock contiene una buena API fluida que funciona muy bien. Aquí hay un ejemplo como adelanto:
mock.expects(once()).method("m").with( or(stringContains("hello"),
stringContains("howdy")) );
Presencié a Steve Freeman y Nat Price dando una excelente charla en JAOO2005 sobre la evolución de la API JMock, y prometieron escribir sobre sus experiencias pero no lo hicieron. Por favor, que alguien secuestre sus compiladores hasta que lo hagan.
Hasta ahora se han visto, APIs fluidas sobre todo para crear objetos, a menudo Value Objects. No estoy seguro de si esta es una característica determinante, aunque sospecho que algo similar aparece en un contexto declarativo. El test clave de fluidez, para nosotros, es la calidad del Lenguaje Específico del Dominio. Cuanto más se parezca el uso de la API a ese lenguaje, más fluida será.
La construcción de una API fluida como esta conduce a algunos hábitos poco habituales en la creación de APIs. Uno de los más obvios son los métodos set que devuelven un valor. (En el ejemplo del pedido, 'with' añade una línea al pedido y devuelve una línea de pedido). El convenio en el mundo de los lenguajes que usan llaves de apertura y cierre (NDT: se refiere a lenguajes con sintaxis tipo C como Java, C++ y C#) es que los métodos que modifican devuelven void, lo cual me gusta porque es fiel al principio de SeparaciónComandoPregunta. Este convenio choca con una interfaz fluida, por tanto me inclino por suspender el convenio en este caso.
Se debería escoger el tipo que se devuelve dependiendo de lo que se necesite para mantener una acción fluida. JMock da gran importancia a mover sus tipos dependiendo de lo que probablemente necesitará a continuación. Una de las grandes ventajas de este estilo es que los sistemas de auto completado (intellisense) te dicen que es lo siguiente que tienes que escribir [tras cada método] - bastante parecido a un wizard en el IDE. En general creo que los lenguajes dinámicos son mejores para los DSLs ya que suelen usar una sintaxis menos rígida. El uso de sistemas de auto completado es, sin embargo, un plus para los lenguajes estáticos.
Uno de los problemas de los métodos con una interfaz fluida es que no tienen mucho sentido por sí mismos. Ir revisando la documentación método a método no parece tener mucho sentido. De hecho por si sólo yo diría que se trata de un método mal nombrado que no comunica su propósito en absoluto. Es sólo en el contexto de una acción fluida cuando muestra sus fortalezas. Una forma de evitar esto es utilizar objetos constructores (Builder objects) que sólo sean usados en este contexto.
Algo que Eric mencionó fue que hasta ahora él ha usado, y visto, interfaces fluidas en torno a la creación de Value Objects. Los Value objects no tienen una identidad de dominio significativa por lo que puedes crearlos y destruirlos fácilmente. Por tanto, la fluidez se basa en la creación de nuevos valores a partir de valores antiguos. En este sentido, el ejemplo del pedido no es el ejemplo típico ya que es una entidad en la ClasificaciónDeEvans.
No he visto muchas interfaces fluidas por ahí todavía, por lo que deduzco que no sabemos mucho sobre sus fortalezas y debilidades. Por lo tanto, cualquier exhortación a usarlas sólo puede ser preliminar - sin embargo estoy convencido de que están suficientemente maduras para seguir experimentando.

