Interfaces marcadoras, atributos y convenciones

Hay ocasiones en que necesitamos añadir algún tipo de metainformación a nuestro código. Se trata de información sobre el código, no tanto sobre lo que hace sino sobre el propio artefacto (clase, método o propiedad), que es necesaria para poder realizar determinadas tareas.

Podemos encontrar ejemplos típicos de esto en aplicaciones MVC, donde de alguna forma necesitamos poder identificar qué clases son controladores, en librerías de serialización para indicar qué propiedades queremos serializar (y cómo), en ORMs para detectar entidades, etc.

Siempre se puede añadir esta metainformación de forma externa, por ejemplo podemos almacenarla en otra clase diferente como si fuese un registro en el que guardamos cada clase con su metainformación, pero lo muchas veces es interesante mantener esa información cerca de la propia clase por motivos de mantenimiento y cohesión. Para ello existen varias alternativas y en este post vamos a ver las tres más frecuentes.

Interfaces marcadoras

Una interfaz marcadora es una interfaz que no contiene ningún método. A priori esto puede parece extraño y algunos incluso lo consideran un antipatrón, ya que un interfaz por definición es un contrato, y un contrato que no dice nada es un contrato un poco raro. Al implementar la interfaz no necesitamos implementar ningún método, simplemente estamos marcando (de ahí el nombre) la clase como una implementación del interfaz.

Existen muchos ejemplos de esto, en Java por ejemplo encontramos el interfaz Serializable y en .NET podemos verlo en algunas implementaciones de Eventos de Dominio.

La ventaja de esta solución es que nos permite aprovechar la comprobación de tipos en tiempo de compilación para asegurarnos de que ciertas operaciones sólo se realizan sobre las clases adecuadas. En el ejemplo de los eventos de dominio tendríamos algo así:

public interface IDomainEvent {}
public class OrderCreated : IDomainEvent { /*...*/ }

public static class DomainEvents
{
  public void Raise<T>(T @event) where T : IDomainEvent { /*... */ }
}

Al utilizar una interfaz marcadora podemos garantizar que no invocamos el método Raise con un objeto que no sea un evento de dominio.

Otra ventaja es que en un IDE moderno podemos buscar fácilmente todas las clases que implementan esa interfaz y localizarlas rápidamente dentro de nuestro proyecto.

La desventaja, aparte de que te pueda gustar más o no eso de tener interfaces vacías, es que la única metainformación que podemos añadir viene determinada por el tipo del interfaz.

Atributos

Los atributos (en .NET) o anotaciones (en Java) son la herramienta canónica que ofrece el lenguaje para añadir metainformación a una clase. En el caso más simple, el atributo únicamente decora una clase marcándola, pero tienen la ventaja de que el propio atributo puede contener información adicional, por lo que podemos incluir más metainformación.

Un ejemplo de esto serían los atributos de routing de ASP.NET MVC, que nos permiten no sólo indicar que la clase es «enrutable» sino también el prefijo de la ruta:

[RoutePrefix("catalog")]
public class ProductController : Controller { /*...*/ }

Una ventaja adicional de los atributos es que son más flexibles que las interfaces porque podemos aplicarlos no sólo sobre clases, sino también sobre métodos, propiedades o campos de la clase.

A cambio, perdemos la seguridad de tipos que nos ofrecían las interfaces marcadoras, por lo que en determinados escenarios pueden ser una solución mejor.

Convenciones

Las dos soluciones que hemos visto hasta ahora, tanto interfaces marcadoras como atributos, requieren una configuración explícita por nuestra parte a la hora de añadir metainformación. Con las convenciones podemos hacer que esta configuración se realice de forma implícita, basándose por ejemplo en el nombre de la case, en el espacio de nombres en el que se encuentra, en su clase base, etc.

Por aquí hemos visto algún ejemplo del uso de convenciones para definir módulos, y si estás acostumbrado a trabajar con cualquier framework moderno, seguramente te hayas encontrado con ellas.

En ASP.NET MVC existen convenciones para detectar los controladores, localizar las vistas, definir las rutas y muchas cosas más.

La ventaja de las convenciones es que nos permiten escribir menos código y podríamos argumentar que son menos intrusivas porque no necesitamos añadir a nuestro código información que no está íntimamente ligada a lo que tiene que hacer.

El problema con las convenciones es que, de todos los métodos que hemos visto, son las menos seguras. Dependiendo del tipo de convención aplicada, no siempre es fácil asegurarnos de que hemos puesto el nombre adecuado a la clase, o de que nos hemos acordado de colocarla en el espacio de nombres adecuado, y generalmente sólo descubriremos el problema en tiempo de ejecución. Si vas a trabajar con convenciones, es muy recomendable que utilices tests para validar las convenciones.

Conclusiones

Cualquiera de las tres técnicas que hemos visto permiten añadir metainformación a una clase. Cada una tiene escenarios en los que es más recomendable.

Si no te hace falta añadir mucha metainformación, las interfaces marcadoras te aportan cierta seguridad gracias a la comprobación de tipos en tiempo de compilación.

Con los atributos pierdes la seguridad de tipos pero puedes añadir información adicional que resulte útil a la hora de procesar las clases (rutas en MVC, mensajes de error al validar, permisos, etc.).

Las convenciones nos ahorran código y dan lugar a un código más limpio, evitándonos tener que añadir a nuestras clases información que es necesaria para otras áreas del sistema y reduciendo el acoplamiento. A cambio, requieren más disciplina para asegurar que las estamos siguiendo y pueden hacer nuestro código más «mágico» y, por tanto, más complicado de entender.