Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Creación de interfaces fluidas

Posted by Gerardo Contijoch en diciembre 3, 2008

Las interfaces fluidas se están poniendo de moda últimamente, o por lo menos eso me parece a mi. Cada vez son mas los frameworks que exponen interfaces fluidas o ‘fluent interfaces’ para facilitar su uso (Rhino Mocks posiblemente sea el ejemplo más famoso). Es difícil definir lo que es una interface fluida sin un buen ejemplo. Básicamente, es una forma de exponer los miembros de clases (o lo que es lo mismo, exponer sus interfaces) de modo que su uso sea sencillo y rápido y natural. Su uso se da principalmente sobre objetos configurables o con muchos parámetros. Veamos de que hablo.

Dada la siguiente clase:

   1: public class Computadora {
   2:
   3:     public string Microprocesador { get; set; }
   4:
   5:     public int CantidadDeRAM { get; set; }
   6:
   7:     public int EspacioEnDisco { get; set; }
   8:
   9:     public bool IncluyeGrabadoraDeDVD { get; set; }
  10:
  11: }

Si quisiésemos configurar nuestra computadora, haríamos lo siguiente:

   1: Computadora miComputadora = new Computadora();
   2:
   3: miComputadora.Microprocesador = "AMD Turion64 X2";
   4: miComputadora.CantidadDeRAM = 4;
   5: miComputadora.EspacioDeDisco = 320;
   6: miComputadora.IncluyeGrabadoraDeDVD = true;

No parece para nada complicado, pero vean como podríamos configurarla utilizando interfaces fluidas:

   1: Computadora miComputadora = new Computadora().ConMicro("AMD Turion64 X2").ConRAM(4).ConEspacio(320).ConGrabadoraDeDVD();

En una sola línea de código logramos lo que nos llevaba 5 antes. Imaginen el beneficio de aplicar esta técnica en objetos con 10 propiedades a configurar. Por supuesto que podemos usar sobrecarga de constructores para lograr la configuración completa del objeto en el momento de instanciarlo, pero los constructores no nos dan la posibilidad de elegir el orden de los parámetros, y siempre estaríamos limitados a un set fijo de ellos que puede no resultarnos práctico.

Las interfaces fluidas no solo se usan para configurar instancias, también podemos encadenar método para que sean ejecutados en secuencia:

   1: miComputadora.Bootear().IniciarSO().Loguearse("usuario", "pass").ChequearMail();

La posibilidad de encadenar métodos le dan a este patrón su otro nombre de ‘method chaining’. Lograr este encadenamiento es muy sencillo, simplemente hay que devolver la instancia actual en cada método y ahí ya tenemos listo el objeto para volver a invocar otro método sobre él. Éste sería uno de los métodos utilizados para configurar la clase Computadora:

   1: public class Computadora {
   2:
   3:     /* ... */
   4:
   5:     public Computadora ConMicro(string micro) {
   6:         this.Microprocesador = micro;
   7:         return this;
   8:     }
   9:     /* ... */
  10: }

Pero no todo termina ahí. ¿qué sucede cuando no tenemos acceso al código de la clase por la cual queremos ‘fluir’ o simplemente no queremos sobrecargarla de métodos utilitarios y opcionales? Para ello veamos otro ejemplo, que por cierto, es mucho mas útil que el anterior.

Imaginemos que en determinado punto de nuestra aplicación debemos enviar un mail, normalmente haríamos algo así:

public void UnMetodo() {
    //...
    EnviarMail("mi_amigo@mail.com", "gerardo.contijoch@mail.com", "Hola amigo!");
    //...
}
public void EnviarMail(string destinatario, string remitente, string mensaje) {
    MailMessage mail = new MailMessage();
    mail.To.Add(destinatario);
    mail.From = new MailAddress(remitente);
    mail.Body = mensaje;
    SmtpClient smtp = new SmtpClient();
    /* ... configuramos smtp ...*/
    smtp.Send(mail);
}

El principal problema de esta solución es que con el fin de encapsular la lógica de envío del mail, estamos creando un método con parámetros fijos y poco flexibles (es decir, hay que recompilar la aplicación si se modifican los parámetros). Podríamos crear sobrecargas pero eso llevaría mucho código extra y seguiría el problema de la flexibilidad si no hay ninguna sobrecarga que nos sea útil (por ejemplo, si queremos especificar mas de un destinatario, cuando todas las sobrecargas aceptan sólo uno). En fin, el mismo problema que teníamos con los constructores anteriormente.

En esta ocasión la técnica consiste en crear una clase extra que haga de wrapper de la clase original y nos ofrezca la fluidez que buscamos:

   1: public class MailMessageFluentInterface {
   2:
   3:     private MailMessage mail = null;
   4:
   5:     public MailMessageFluentInterface() : this(null) {}
   6:
   7:     public MailMessageFluentInterface(MailMessage mail) {
   8:         if (mail == null) {
   9:             this.mail = new MailMessage();
  10:         } else {
  11:             this.mail = mail;
  12:         }
  13:     }
  14:
  15:     public MailMessageFluentInterface Para(string direccionDestinatario) {
  16:         this.mail.To.Add(direccionDestinatario);
  17:         return this;
  18:     }
  19:
  20:     public MailMessageFluentInterface Para(MailAddress direccionDestinatario) {
  21:         this.mail.To.Add(direccionDestinatario);
  22:         return this;
  23:     }
  24:
  25:     public MailMessageFluentInterface De(string direccionRemitente, string remitente) {
  26:         var rte = new MailAddress(direccionRemitente, remitente);
  27:         return this.De(rte);
  28:     }
  29:
  30:     public MailMessageFluentInterface De(MailAddress direccionRemitente) {
  31:         this.mail.From = direccionRemitente;
  32:         return this;
  33:     }
  34:
  35:     public MailMessageFluentInterface ConAsunto(string asunto) {
  36:         this.mail.Subject = asunto;
  37:         return this;
  38:     }
  39:
  40:     public MailMessageFluentInterface ConMensaje(string mensaje) {
  41:         this.mail.Body = mensaje;
  42:         return this;
  43:     }
  44:
  45:     public MailMessageFluentInterface ConMensajeHtml(string mensajeHtml) {
  46:         this.mail.IsBodyHtml = true;
  47:
  48:         return this.ConMensaje(mensajeHtml);
  49:     }
  50:
  51:     public void Enviar() {
  52:         /* Codigo de ejemplo simplificado */
  53:         SmtpClient smtp = new SmtpClient();
  54:         smtp.Send(this.mail);
  55:     }
  56: }

De modo que el código se simplificaría pudendo quedar así:

   1: public void UnMetodo() {
   2:     //...
   3:     new MailMessageFluentInterface().De("gerardo.contijoc@mail.com", "Gerardo Contijoch").Para("un_amigo@mail.com").ConMensaje("Hola amigo!").Enviar();
   4:     //...
   5: }

Sin embargo esta solución no es de lo mejor. Noten que ahora tenemos que tener conocimiento de la nueva clase y tenemos que instanciarla nosotros mismos, cuando en realidad queremos crear un mail. No es del todo intiutivo. ¿No seria ideal que la propia clase MailMessage nos simplificara la tarea?

Bueno, eso es posible, gracias a los métodos de extensión:

   1: public static class MailExtensions {
   2:     public static MailMessageFluentInterface Configurar(this MailMessage mail) {
   3:         return new MailMessageFluentInterface(mail);
   4:     }
   5: }
   6:
   7: /* ... */
   8: public void UnMetodo() {
   9:     //...
  10:      new MailMessage().Configurar().De("gerardo.contijoc@mail.com", "Gerardo Contijoch").Para("un_amigo@mail.com").ConMensaje("Hola amigo!").Enviar();
  11:     //...
  12: }

Otra opción válida sería hacer que todos los métodos de MailMessageFluentInterface sean métodos de extensión de la clase MailMessage (haciendo innecesaria la creación de MailMessageFluentInterface y la llamada a Configurar() ), pero eso llenaría a MailMessage de métodos redundantes (debido a que ya existen las propiedades) que solo tendríamos interés de usar cuando estamos creando un mail y nada mas. Yo prefiero dejar las cosas por separado e independizar el contenido de ambas clases.

Y eso es todo por el momento. Me quedó en el tintero un par de ideas sobre la creación de interfaces fluidas más complejas (y útiles), pero como son solo ideas, por el momento me las quedo yo.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Una respuesta to “Creación de interfaces fluidas”

  1. […] Creación de interfaces fluidas […]

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

 
A %d blogueros les gusta esto: