¿Cuándo ir con fluidez en C #?

76

En muchos aspectos, me gusta mucho la idea de las interfaces Fluent, pero con todas las características modernas de C # (inicializadores, lambdas, parámetros con nombre) me encuentro pensando: "¿vale la pena?", y "¿Es este el patrón correcto para usar? ". ¿Alguien podría darme, si no es una práctica aceptada, al menos su propia experiencia o matriz de decisiones sobre cuándo usar el patrón Fluent?

Conclusion:

Algunas buenas reglas de oro de las respuestas hasta el momento:

  • Las interfaces fluidas son de gran ayuda cuando se tienen más acciones que configuradores, ya que las llamadas se benefician más del traspaso de contexto.
  • Las interfaces fluidas deben considerarse como una capa sobre la parte superior de una API, no como el único medio de uso.
  • Las características modernas, como lambdas, inicializadores y parámetros con nombre, pueden funcionar mano a mano para hacer que una interfaz fluida sea aún más amigable.

Este es un ejemplo de lo que quiero decir con las características modernas que lo hacen sentir menos necesario. Tomemos, por ejemplo, una interfaz fluida (quizás pobre) que me permite crear un empleado como:

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

Se podría escribir fácilmente con inicializadores como:

Employees.Add(new Employee()
              {
                  FirstName = "Peter",
                  LastName = "Gibbons",
                  Manager = new Employee()
                            {
                                 FirstName = "Bill",
                                 LastName = "Lumbergh",
                                 Title = "Manager",
                                 Department = "Y2K"
                            }
              });

También podría haber usado parámetros nombrados en los constructores en este ejemplo.

    
pregunta Andrew Hanlon 19.04.2011 - 10:44

6 respuestas

26

Escribiendo una interfaz fluida (tengo

En otras palabras, si su código se lee mucho más de lo que está escrito (¿y qué código no lo es?), debería considerar la creación de una interfaz fluida.

Las interfaces fluidas tienen más que ver con el contexto, y son mucho más que simples formas de configurar objetos. Como se puede ver en el enlace anterior, usé una API fluida para lograr:

  1. Contexto (por lo tanto, cuando normalmente haces muchas acciones en una secuencia con la misma cosa, puedes encadenar las acciones sin tener que declarar tu contexto una y otra vez).
  2. Descubrimiento (cuando vas a objectA. , intellisense te da muchos consejos. En mi caso anterior, plm.Led. te da todas las opciones para controlar el LED incorporado y plm.Network. te da las cosas que puede hacer con la interfaz de red. plm.Network.X10. le brinda el subconjunto de acciones de red para dispositivos X10. No obtendrá esto con inicializadores de constructor (a menos que desee construir un objeto para cada tipo de acción diferente, lo cual no es idiomático).
  3. Reflexión (no se usa en el ejemplo anterior): la capacidad de tomar una expresión LINQ pasada y manipularla es una herramienta muy poderosa, particularmente en algunas API de ayuda que he creado para pruebas unitarias. Puedo pasar una expresión de obtención de propiedades, crear un montón de expresiones útiles, compilarlas y ejecutarlas, o incluso usar la función de obtención de propiedades para configurar mi contexto.

Una cosa que normalmente hago es:

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

No veo cómo puedes hacer algo así sin una interfaz fluida.

Editar 2 : También puedes hacer mejoras de legibilidad realmente interesantes, como:

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();
    
23

Scott Hanselman habla sobre esto en Episodio 260 de su podcast Hanselminutes con Jonathan Carter. Explican que una interfaz fluida es más como una interfaz de usuario en una API. No debe proporcionar una interfaz fluida como único punto de acceso, sino más bien proporcionarla como una especie de código de interfaz de usuario en la parte superior de la "interfaz API normal".

Jonathan Carter también habla un poco sobre el diseño de API en su blog .

    
respondido por el Kristof Claes 19.04.2011 - 15:28
14

Llego un poco tarde, pero pensé que iba a opinar sobre esto ...

Las interfaces fluidas son características muy potentes para proporcionar dentro del contexto de su código, cuando no con el razonamiento "correcto".

Si su objetivo es simplemente crear cadenas masivas de códigos de una línea como una especie de pseudo-caja negra, entonces probablemente esté ladrando el árbol equivocado. Si, por otro lado, lo está utilizando para agregar valor a su interfaz API al proporcionar un medio para encadenar llamadas a métodos y mejorar la legibilidad del código, entonces con un gran esfuerzo y una buena planificación, creo que el esfuerzo vale la pena.

Evitaría seguir lo que parece convertirse en un "patrón" común al crear interfaces fluidas, donde nombras todos tus métodos fluidos "con" -algo, ya que roba una interfaz API potencialmente buena de su contexto, y por lo tanto su valor intrínseco.

La clave es pensar en la sintaxis fluida como una implementación específica de un lenguaje específico del dominio. Como un buen ejemplo de lo que estoy hablando, eche un vistazo a StoryQ, que emplea la fluidez como un medio para expresar un DSL de una manera muy valiosa y flexible.

    
respondido por el S.Robins 24.11.2011 - 07:23
5

Nota inicial: no estoy de acuerdo con una suposición en la pregunta y saco mis conclusiones específicas (al final de esta publicación) a partir de eso. Debido a que esto probablemente no sea una respuesta completa y abarcadora, lo estoy marcando como CW.

Employees.CreateNew().WithFirstName("Peter")…
     

Se podría escribir fácilmente con inicializadores como:

Employees.Add(new Employee() { FirstName = "Peter", … });

En mi opinión, estas dos versiones deberían significar y hacer cosas diferentes.

  • A diferencia de la versión no fluida, la versión fluida oculta el hecho de que el nuevo Employee también es Add ed a la colección Employees ; solo sugiere que un nuevo objeto es Create d .

  • El significado de ….WithX(…) es ambiguo, especialmente para las personas que vienen de F #, que tiene una palabra clave with para expresiones de objetos : pueden interpretar obj.WithX(x) como un objeto nuevo que se deriva de obj que es idéntico a obj , excepto por su propiedad X , cuyo valor es x . Por otro lado, con la segunda versión, está claro que no se crean objetos derivados y que todas las propiedades están especificadas para el objeto original.

….WithManager().With…
  • Este ….With… tiene otro significado: cambiar el "enfoque" de la inicialización de la propiedad a un objeto diferente. El hecho de que su API fluida tenga dos significados diferentes para With está dificultando la interpretación correcta de lo que está sucediendo ... por lo que tal vez haya utilizado la sangría en su ejemplo para demostrar el significado deseado de ese código. Sería más claro así:

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the 'Manager' property appears inside the parentheses,
    //                     like with 'WithName', where the 'Name' is also inside the parentheses 
    

Conclusiones: "Ocultar" una función de lenguaje bastante simple, new T { X = x } , con una API fluida ( Ts.CreateNew().WithX(x) ) obviamente se puede hacer, pero:

  1. Se debe tener cuidado de que los lectores del código fluido resultante aún entiendan lo que hace exactamente. Es decir, la API fluida debe ser transparente en su significado y no ambigua. Diseñar una API de este tipo puede ser más labor de lo que se anticipa (podría tener que probarse para que sea fácil de usar y aceptar), y / o ...

  2. diseñarlo puede requerir más trabajo del que es necesario: en este ejemplo, la API fluida agrega muy poca "comodidad para el usuario" sobre la API subyacente (una característica del idioma). Se podría decir que una API fluida debería hacer que la característica API / idioma subyacente sea "más fácil de usar"; es decir, debería ahorrarle al programador una cantidad considerable de esfuerzo. Si es solo otra forma de escribir lo mismo, probablemente no valga la pena, porque no hace la vida del programador más fácil, pero solo hace que el trabajo del diseñador sea más difícil (vea la conclusión # 1 a la derecha).

  3. Los dos puntos anteriores asumen que la API fluida es una capa sobre una API o función de idioma existente. Esta suposición puede ser otra buena guía: una API fluida puede ser una forma adicional de hacer algo, no la única. Es decir, podría ser una buena idea ofrecer una API fluida como una opción de "inclusión".

respondido por el stakx 10.08.2013 - 19:25
2

Me gusta el estilo fluido, expresa la intención muy claramente. Con el ejemplo de inicializador de objetos que tienes después, debes tener definidores de propiedades públicas para usar esa sintaxis, no con el estilo fluido. Dicho esto, con su ejemplo no gana mucho con los configuradores públicos porque casi se ha ido para un método de estilo set / get en java-esque.

Lo que me lleva al segundo punto, no estoy seguro de si usaría el estilo fluido de la forma en que lo has hecho, con un montón de establecedores de propiedades, probablemente usaría la segunda versión para eso, lo encuentro mejor cuando tienes muchos verbos para encadenar, o al menos muchas tareas en lugar de configuraciones.

    
respondido por el Ian 19.04.2011 - 16:27
1

No estaba familiarizado con el término interfaz fluida , pero me recuerda a un par de API que he utilizado, incluido LINQ .

Personalmente, no veo cómo las características modernas de C # evitarían la utilidad de este enfoque. Prefiero decir que van de la mano. P.ej. es aún más fácil lograr dicha interfaz utilizando métodos de extensión .

Quizás aclare su respuesta con un ejemplo concreto de cómo se puede reemplazar una interfaz fluida usando una de las características modernas que mencionó.

    
respondido por el Steven Jeuris 19.04.2011 - 15:22

Lea otras preguntas en las etiquetas