language agnostic - Interfaces fluidas: encadenamiento de métodos
language-agnostic design-patterns (9)
AFAIK, el término interfaz fluida no especifica una tecnología o marco específico, sino más bien un patrón de diseño. Wikipedia tiene un amplio ejemplo de interfaces fluidas en C♯ .
En un método de configuración simple, no devuelve void
sino this
. De esta forma, puede encadenar todas las declaraciones en ese objeto que se comporten así. Aquí hay un ejemplo rápido basado en su pregunta original:
public class JohnBuilder
{
private IList<string> languages = new List<string>();
private IList<string> fluentInterfaces = new List<string>();
private string butHow = string.Empty;
public JohnBuilder AddSmartCode(string language)
{
this.languages.Add(language);
return this;
}
public JohnBuilder WithFluentInterface(string fluentInterface)
{
this.fluentInterfaces.Add(fluentInterface);
return this;
}
public JohnBuilder ButHow(string butHow)
{
this.butHow = butHow;
return this;
}
}
public static class MyProgram
{
public static void Main(string[] args)
{
JohnBuilder johnBuilder = new JohnBuilder().AddSmartCode("c#").WithFluentInterface("Please").ButHow("Dunno");
}
}
El encadenamiento de métodos es la única forma que conozco de construir interfaces fluidas.
Aquí hay un ejemplo en C #:
John john = new JohnBuilder()
.AddSmartCode("c#")
.WithfluentInterface("Please")
.ButHow("Dunno");
Assert.IsNotNull(john);
[Test]
public void Should_Assign_Due_Date_With_7DayTermsVia_Invoice_Builder()
{
DateTime now = DateTime.Now;
IInvoice invoice = new InvoiceBuilder()
.IssuedOn(now)
.WithInvoiceNumber(40)
.WithPaymentTerms(PaymentTerms.SevenDays)
.Generate();
Assert.IsTrue(invoice.DateDue == now.AddDays(7));
}
Entonces, ¿cómo otros crean interfaces fluidas? ¿Cómo lo creas? ¿Qué idioma / plataforma / tecnología se necesita?
Así es como he construido mis llamadas interfaces fluidas o mi única fuente en él
Tokenizer<Bid> tkn = new Tokenizer<Bid>();
tkn.Add(Token.LambdaToken<Bid>("<YourFullName>", b => Util.CurrentUser.FullName))
.Add(Token.LambdaToken<Bid>("<WalkthroughDate>",
b => b.WalkThroughDate.ToShortDateString()))
.Add(Token.LambdaToken<Bid>("<ContactFullName>", b => b.Contact.FullName))
.Cache("Bid")
.SetPattern(@"</w+>");
Mi ejemplo requiere .net 3.5, pero esa es solo la causa de mi lambda. Como Brad señaló, puedes hacer esto en cualquier versión de .net. Aunque creo que las de Lambda son para posibilidades más interesantes como esta.
======
Algunos otros buenos ejemplos son la API de criterios de nHibernate, también existe una extensión de nhibernate para configurar el nhibernate pero nunca lo he usado
Encontré una forma de encadenar el método polimórfico en las interfaces fluidas con seguridad de tipo aplicada en tiempo de compilación utilizando constructores genéricos y métodos de extensión genéricos.
Los métodos de extensión son geniales :)
Hace un tiempo tuve las mismas dudas que tienes ahora. Investigué un poco y ahora estoy escribiendo una serie de publicaciones en blogs sobre tecnología para diseñar una interfaz fluida.
Compruébalo en:
Directrices para el diseño de interfaz fluida en C # parte 1
Tengo una sección sobre Encadenamiento X que te puede interesar.
En los siguientes mensajes hablaré sobre ello de una manera más profunda.
Atentamente,
André Vianna
La idea central detrás de la construcción de una interfaz fluida es la legibilidad: alguien que lea el código debería ser capaz de entender lo que se está logrando sin tener que profundizar en la implementación para aclarar detalles.
En los lenguajes OO modernos como C #, VB.NET y Java, el encadenamiento de métodos es una forma de lograr esto, pero no es la única técnica; otros dos son clases de fábrica y parámetros nombrados.
Tenga en cuenta también que estas técnicas no son mutuamente excluyentes: el objetivo es maximizar la legibilidad del código, no la pureza del enfoque.
Método de encadenamiento
La idea clave detrás del método de encadenamiento es nunca tener un método que devuelva el vacío, sino devolver siempre algún objeto, o más a menudo, alguna interfaz, que permita hacer más llamadas.
No es necesario que devuelva necesariamente el mismo objeto al que se llamó el método, es decir, no siempre necesita "devolver esto".
Una técnica de diseño útil es crear una clase interna: siempre sufijo esto con "Expresión", que expone la API fluida, lo que permite la configuración de otra clase.
Esto tiene dos ventajas: mantiene la API fluida en un solo lugar, aislada de la funcionalidad principal de la clase, y (debido a que es una clase interna) puede jugar con las entrañas de la clase principal en formas que otras clases no pueden.
Es posible que desee utilizar una serie de interfaces para controlar qué métodos están disponibles para el desarrollador en un momento dado.
Clases de fábrica
A veces querrá construir una serie de objetos relacionados; los ejemplos incluyen la API de NHibernate Criteria, las restricciones de expectativas de Rhino.Mocks y la nueva sintaxis de NUnit 2.4.
En ambos casos, tiene los objetos reales que está almacenando, pero para facilitar su creación, existen clases de fábrica que proporcionan métodos estáticos para fabricar las instancias que necesita.
Por ejemplo, en NUnit 2.4 puedes escribir:
Assert.That( result, Is.EqualTo(4));
La clase "Is" es una clase estática llena de métodos de fábrica que crean restricciones para la evaluación por parte de NUnit.
De hecho, para permitir los errores de redondeo y otras imprecisiones de los números de coma flotante, puede especificar una precisión para la prueba:
Assert.That( result, Is.EqualTo(4.0).Within(0.01));
(Disculpas anticipadas: mi sintaxis puede estar desactivada).
Parámetros nombrados
En los idiomas que los admiten (incluidos Smalltalk y C # 4.0) los parámetros nombrados proporcionan una forma de incluir una "sintaxis" adicional en una llamada a un método, mejorando la legibilidad.
Considere un método hipotético de Guardar () que toma un nombre de archivo y permisos para aplicar al archivo después de guardar:
myDocument.Save("sampleFile.txt", FilePermissions.ReadOnly);
con parámetros nombrados, este método podría verse así:
myDocument.Save(file:"SampleFile.txt", permissions:FilePermissions.ReadOnly);
o, con mayor fluidez:
myDocument.Save(toFile:"SampleFile.txt", withPermissions:FilePermissions.ReadOnly);
La interfaz fluida se logra en la programación orientada a objetos al devolver siempre desde sus métodos la misma interfaz que contiene el método. En consecuencia, puede lograr este efecto en java, javascript y sus otros lenguajes orientados a objetos favoritos, independientemente de la versión.
He encontrado que esta técnica es más fácil de lograr mediante el uso de interfaces:
public interface IFoo
{
IFoo SetBar(string s);
IFoo DoStuff();
IFoo SetColor(Color c);
}
De esta forma, cualquier clase concreta que implemente la interfaz obtiene las capacidades de encadenamiento de método fluido. FWIW .. Escribí el código anterior en C # 1.1
Encontrará esta técnica en la jQuery API
La palabra clave dinámica en C # 4.0 permitirá escribir constructores de estilos dinámicos. Eche un vistazo al siguiente article sobre la construcción de objetos JSON.
Me vienen a la mente un par de cosas que son posibles en .Net 3.5 / C # 3.0:
Si un objeto no implementa una interfaz fluida, puede usar los Métodos de extensión para encadenar sus llamadas.
Es posible que pueda utilizar la inicialización del objeto para simular con fluidez, pero esto solo funciona en el momento de creación de instancias y solo funcionaría para los métodos de argumento único (donde la propiedad es solo un setter). Esto me parece hackeo, pero ahí está.
Personalmente, no veo nada de malo en utilizar el encadenamiento de funciones si está implementando un objeto de compilación. Si el objeto generador tiene métodos de encadenamiento, mantiene limpio el objeto que está creando. Solo un pensamiento.
Puede crear una interfaz fluida en cualquier versión de .NET o cualquier otro lenguaje orientado a objetos. Todo lo que necesita hacer es crear un objeto cuyos métodos siempre devuelvan el objeto en sí.
Por ejemplo en C #:
public class JohnBuilder
{
public JohnBuilder AddSmartCode(string s)
{
// do something
return this;
}
public JohnBuilder WithfluentInterface(string s)
{
// do something
return this;
}
public JohnBuilder ButHow(string s)
{
// do something
return this;
}
}
Uso:
John = new JohnBuilder()
.AddSmartCode("c#")
.WithfluentInterface("Please")
.ButHow("Dunno");