Interfaces en .Net, tus nuevas mejores amigas

Uno de los terminos mas sobreutilizados en el mundo del software es el de «Interfaz», en este post vamos a utilizar la siguiente definición tomada de WikiPedia: «Interfaz es frecuentemente utilizado para definir un tipo abstracto que no contiene datos ni código, pero que define conducta a través de firmas de métodos».

Aqui vamos a discutir en forma practica las interfaces utilizando C# y veremos por que las necesitamos para implementar adecuadamente arquitecturas como DDD o Limpia.

Lo primero que vamos a fijar, es que las interfaces establecen un contrato que debe ser cumplido. Supongamos por ejemplo que eres celíaco y por tanto no puedes consumir gluten, si vas al supemercado, en tu carro de compras vas a colocar productos que marquen «Libre de Gluten», esta etiqueta implica un contrato entre el productor y tú de que el producto no va a hacerte daño y por tanto tu solo vas a aceptar productos que tenga dicha etiqueta.

Lo Básico de las Interfaces:

Si, ya se lo que estás pensando, tu eres desarrollador, no quieres ejemplos de supermercados y cosas así !tu quieres ver código!, así que muy bien, vamos con código. Vamos ahora a imaginar que tu y yo estamos en un equipo desarrollando una aplicación para un concurso en una universidad, en este concurso pueden participar Estudiantes, Profesores y Administrativos. Entre las propiedades de cada rol tenemos campos comunes como nombre, direccion, etc. y otras no comunes como año (solo estudiantes) o departamento (solo administrativos). La clase Profesor Luce así:

public class Profesor
{
    public string Identificacion { get; set; }
    public string Nombre { get; set; }
    public string Calle { get; set; }
    public string Telefono { get; set; }
    ...
}

Las clases Estudiante y Administrativo son similares, aunque como ya comentamos tienen algunas propiedades distintas.

En el proyecto te han pedido un metodo para enviar comunicaciones a los participantes del concurso y construyes este:

public void EnviarComunicacion(string identificacion, string nombre, 
            string calle, string telefono)
{
      ...
}

A ver, no me parece muy agradable de tu parte este metodo, cuando yo lo invoque voy a tener que pasar un montón de parametros y mis invocaciones van a quedar larguisimas. Además si te piden agregar un parametro, no solo tu vas a modificar tu método, tambien yo tendré que modificar mis invocaciones.

¿Que tal crear uno así?

public void EnviarComunicacion(Profesor unProfesor)
{
      ...
}

Hmmm. Luce mejor, pero solo sirve para profesores. No irás a crear uno por cada tipo ¿verdad?.

Que tal así:

public void EnviarComunicacion(object participante)
{
      ...
}

Parece mejor. Pero, ¿Y si alguien hace esta invocación?

EnviarComunicacion(1);

El camino correcto es crear un contrato entre ambas partes y por tanto ¡una interfaz!. Creemos la nuestra (lo común es que el nombre de una interfaz comience con una I):

public interface IParticipante { }

Ahora modificariamos nuestras tres clases para especificar que cumplen con la interfaz:

public class Profesor: IParticipante

y de esta manera, podemos utilizar nuestro contrato en el método:

public void EnviarComunicacion(IParticipante participante)

y estas invocaciónes serían validas:

Profesor unProfesor = new Profesor();
Estudiante unEstudiante =new Estudiante();
EnviarComunicacion(unProfesor);
EnviarComunicacion(unEstudiante);

y sin embargo lo siguiente, ni siquiera compilaría:

EnviarComunicacion(1);

Con esto hemos visto que las interfaces garantizan el contrato, sin embargo nuestro contratos deberían ir mas allá. Para mejorar nuestro contrato, necesitamos especificar cuales son las propiedades que deben tener los objetos para cumplir con el contrato IParticipante, por ejemplo:

public interface IParticipante
{
    string Identificacion { get; set; }
    string Nombre { get; set; }
    string Calle { get; set; }
    string Telefono { get; set; }
}

¿y que hemos logrado al añadir las propiedades?
1) Las clases creadas con la interfaz IParticipante están obligadas a implementar todos los métodos definidos en la interfaz.
2) Para cualquier objeto que implemente la interfaz, ¡ya conocemos sus propiedades!

Al extender nuestra interfaz con las propiedades ahora nuestro método conoce más y código como este es perfectamente válido:

public void EnviarComunicacion(IParticipante participante)
{
    string unParticipante = participante.Identificacion 
	+ participante.Nombre;
}

Observa que esto funciona sin importar si es un profesor, un estudiante o un administrativo. Al hacer esto hemos creado una abstracción, en lugar de pensar en profesores, alumnos y administrativos, pensamos únicamente en participantes.  En este caso decimos que IParticipante es «abstracta» y que Profesor, Estudiante y Administrativo son clases «concretas».

Otro beneficio que obtenemos al utilizar así interfaces es el de desacoplar el método y las clases, el método EnviarComunicación no depende de una clase en particular, si por ejemplo necesitamos crear la clase «Contratista» y enviarle comunicaciones, solo necesitamos que cumpla con la interfaz IParticipante, ¡Magia pura!.

Se lo que Eres Capaz de Hacer:

Una interfaz no solo puede especificar propiedades sino también firmas de métodos, lo cual permite conocer anticipadamente las acciones que un objeto puede realizar.

Continuando nuestro ejemplo supogamos ahora que las comunicaciones no solo se van a enviar en texto, sino que tambien queremos dar la posibilidad de enviarlas en Json en XML y en iconos de WhatsApp (es que ahora nos empeñamos en estar hablando en jeroglificos) , la conformacion de estos mensajes no queremos que sea general, sino que cada clase (profesor, estudiante, etc.) va a proporcionar su mecanismo para generar esta información, sin embargo la invocación del método de formateo la va a hacer  el método EnviarComunicación. ¿Solución? extender nuestra interfaz:

public interface IParticipante
{
  string Identificacion { get; set; }
  string Nombre { get; set; }
  string Calle { get; set; }
  string Telefono { get; set; }
  string Formatear(string tipoFormateo);
}

Acabamos de añadir un método a nuestra interfaz, por lo tanto las clases basadas en esta interfaz deben implementarlo también:

public class Profesor:IParticipante
{
    ...
    public string Formatear(string tipoFormateo)
    {
        ...
    }
}

Y por tanto ya conocemos lo que nuestras clase son capaces de ejecutar y podemos invocar el método:

public void EnviarComunicacion(IParticipante participante) 
{
   string textoXml = participante.Formatear("XML"); 
}

Ya no tenemos que preocuparnos, una única invocación sirve para las tres  clases concretas. (en próximos posts hablaremos en detalle de la inversión de dependencias).

Fabricando Instancias Concretas:

Un problema al que vamos a enfrentarnos, es la creación de los objetos. Y no, esto no es válido:

IParticipante p = new IParticipante();

Siempre debemos crear clases concretas, por lo que esto si es válido:

IParticipante p = new Profesor();
IParticipante e = new Estudiante();

Ahora bien, supongamos que la creación inicial de un profesor, estudiante o administrativo conlleva una serie de operaciones iniciales, la mayoria de ellas comunes y algunas distintas.  ¿tenemos que repetir las instrucciones en cada clase?.  Una alternativa es utilizar una factoría.  Una factoría no es mas que un mecanismo en el cual delegar la creación de los objetos, puedes investigar mas leyendo sobre el patrón factoría.

Construyamos una fábrica muy simple mediante un método:

public IParticipante CrearParticipante(string tipo, string identificacion)
{
   IParticipante nuevoParticipante;
   switch (tipo)
   {
      case "A":
         nuevoParticipante = new Administrativo();
         break;
      case "E":
         nuevoParticipante = new Estudiante();
         break;
      default:
         nuevoParticipante = new Profesor();
         break;
   }
   nuevoParticipante.Identificacion = identificacion;
   return nuevoParticipante;
}

Ahora nuestra lógica de creación está en un único método por lo que tenemos un punto comun de modificación y mejora. Observa como el método sabe el tipo de objeto a devolver en incluso hace una operacion común que es la de asignar el identificador. Nuestro metodo lo invocaríamos de esta manera:

IParticipante nuevoEstudiante = CrearParticipante("E","123456");
IParticipante nuevoProfesor = CrearParticipante("P", "123456");

 

Espero que esta introducción a las interfaces te haya resultado interesante ya que es basica para otros temas que iremos explicando.

Esta entrada fue publicada en .Net, Arquitectura de Aplicaciones, C#, Interfaces, Patrones, Programacion, Uncategorized. Guarda el enlace permanente.

2 respuestas a Interfaces en .Net, tus nuevas mejores amigas

  1. Pingback: Inversión e Inyección de Dependencias en .Net | Arquitecto Sin Bloques

Deja un comentario