sábado, 29 de septiembre de 2012

Entendiendo las Interfaces


ENTENDIENDO LAS INTERFACES

Has visto muchas veces que se habla de interfaces, en muchas partes, el concepto es muy simple, la utilidad enorme, aunque quiza pase por debajo de la mesa.

Explico con una idea.

Linux o Windows, se comunican internamente con sus discos duros de una forma genérica, es decir, sin considerar las capacidades privadas de cada disco duro, para lograr eso el sistema operativo tiene un programa intermedio que ofrece un conjunto de funciones comunes para todos los discos siendo el programa quien lidie con los diferentes modelos de discos mediante el uso de drivers.  De este modo tu solo copias el bendito byte sin que te importe por cual driver pasó. Ese "programa"  es una interfaz.

En diseño de software hay una equivalencia, y lo explico con otro ejemplo:

Tienes estas clases:  CPerro, CGato, CPalabras.

Supon que también tienes un gestionador global que llamaremos CGestionador, que no le importa si son perros, gatos, numeros o palabras, lo que le importa es "cuantos tiene".  

Pues bien,  entonces, la clase CPerros, CGatos, CPalabras necesitarán una funcion llamada "getContador()", ahora...cómo cuentas los perros, como cuentas los gatos..ese es problema de cada clase, lo que al gestionador le interesa es: CUANTOS HAY.

SOLUCION TERRORISTA QUE HOY EN DIA MUCHA, MUCHA GENTE USA:

Tu pudieras decir, usando la mentalidad de los años 80's...."ahhhh talisto...yo pongo una funcion getContador y SE ACABO EL PROBLEMA"...asi, como un día estabas de buen animo, creaste:
clase Perro {   
   function contarPerros(){  return 8; }
}
luego se te cayo el cafe y andabas enojado y pusiste, para la clase gato:
clase Perro {   
   function gatosCounter(){  return 3; }
}
y asi...cada una distinta por las demas clases, ahora el terror...el gestionador gracias a tu metodo terrorista debera tener un IF por cada tipo:
gestionador:
if(quierePerros) then  n = n + perrito.contarperros();
if(quiereGatos) then  n = n + gatos.gatosCounter();

precioso.

SOLUCION DECENTE, USANDO INTERFACES:

Creo que te habras dado cuenta de lo horrible del codigo anterior, bueno uno ve eso todos los dias y la gente se enoja cuando uno les dice que ESTA MALO.

Cómo implementariamos las interfaces para resolver ese problema de contar perritos, gatos, palabras y numeros usando un gestionador ?

primero definimos una interfaz, en PHP se hace creando un archivo asi:

<?php

  interface IContador {
      public function getContador();
  }
?>
La interfaz solo define las funciones que tu necesitas, no las implementa, solo dibuja el esquema, y por tanto, el gestionador requerira a un objeto que tenga esta interfaz en su definicion, si el gestionador (o mejor dicho, el consumidor de la interfaz) detecta que la instancia que esta manejando NO TIENE esta interfaz, pues simplemente la aborta y no la usa.

en lenguajes maduros y reales como Java, C++ y otros, las interfaces son duramente controladas, por ejemplo si estas codificando y le pasas a una funcion un objeto por argumento y esta funcion requiere que el objeto implemente tal o cual interfaz entonces dara un error de compilación si el objeto no implementa la interfaz.  En PHP el caso no es tan fuerte, solo dara errores si una clase no implementa todos los metodos de la interfaz, pero para validar que un objeto implementa la interfaz hay que usar codigo para enumerar las interfaces que tal o cual objeto trae consigo en su clase base, es util, pero a veces es necio, prefiero que un compilador me diga de una vez el error antes que me lo diga un cliente por algun descuido mio como programador.

porque se llama "IContador", porque por convención, llamamos con una "I" de "Interfaz" a todo aquello que será una interfaz. Podría llamarse "RequerimientoEsquemaContador"...nos quedamos con IContador mejor..

ahora, a donde va esta interfaz, como verás, la clase CPerro se llama "el realizador de la interfaz", es aquella clase que implementa la interfaz, por eso se usa la palabra clave "implements":

class CPerro implements IContador {
    public function getContador(){
      return $this->calcularCuantosPerritosHay();
   }
}

Si por error, tu colocaras mal el nombre de una funcion declarada como "interface", o si la olvidaras colocar del todo entonces PHP te advertirá de que la clase CPerro no tiene el metodo requerido por la interfaz.  Esto ayuda a crear codigo ordenado.

Las demás clases, CGato, CNumeros, CPalabras, quiza tengan su propia manera de contar...eso no nos importa, lo que si nos importa es que esas clases implementen la interfaz y que provean los metodos que el gestionador (el consumidor de la interfaz requiere).

// esta clase es distinta a CPerro, pero, implementa la interfaz, y por tanto podrá ser usada 
// por el gestionador:
class CGato implements IContador {
   public $contadorDeGatos;
     public function getContador(){
      return $this->contadorDeGatos;
   }
}
class CPalabras implements IContador {
    public function getContador(){
      return count(array("zorro","sapo","culebra","coco"));
   }
}

Ahora que ya implementamos la interfaz en todas aquellas clases que queremos contar, creamos el Gestionador, que nos dira cuantas cosas tenemos, sin importar lo que ellas son en concepto:

La clase consumidora de la interfaz no lleva la palabra "implements" como las otras porque esta no va a ser algo para contar...no vamos a contar "Gestionadores", en cambio sera el gestionador quien contará...

class CGestionador {
   public function contarloTodo(){
          
     $perros = new CPerro();
     $gatos = new CGato();
     $palabras = new CPalabras();
    
     $total = $total + $perros->getContador(); 
     $total = $total + $gatos->getContador(); 
     $total = $total + $palabras->getContador(); 

     return $total;
   }
}

Queda bonito.  Queda limpio y ordenado.  Aunque la interfaz no es para alegrarse por lo bonito que quedo, si lo es para controlar cosas muy grandes, como ves en los diagramas de la libreria Cruge o EYui de mi repositorio de codigo fuente.

Cómo se grafican las interfaces en UML ? 

en la siguiente grafica, veras que de la clase CGestionador sale una flecha sin cabeza rellena y con linea discontinua, esta es la notacion para indicar que "CGestionador" "depende de" la interfaz "IContador", eso se traduce en que con este grafico sabemos que CGestionador espera que CPerro, CGato y CPalabras tengan en su codigo las funciones que la interfaz trae consigo.  Esto significa que CGestionador puede "contar" cualquier cosa que implemente esta interfaz.

Las flechas con cabeza rellena que van de CPerro a IContador, tienen una etiqueta que dice "realize", esto significa que esas clases (CPerro, etc) "Realizan" o "Implementan" a las funciones de IContador, son consideradas las realizadoras de la interfaz, mientras que CGestionador es un consumidor de la interfaz.

Ejemplos del mundo real:







5 comentarios:

  1. Muy bien explicado Christian, excelente aporte!

    ResponderEliminar
    Respuestas
    1. que bueno ojala te ayude en tus siguientes proyectos. las interfaces son la base del software abstracto, es decir, las bases del buen software, aquel que es independiente de aquello que quieres resolver explicitamente.

      ahora cuando veas un diagrama de arquitectura de linux veras mayor sentido a porque usan tanto las interfaces, las cuales le dan el poder a los SO del dia de hoy, a todos, no solo a linux, sin ellas viviriamos en los años 60's.

      Eliminar
  2. Que tal Christian? gracias por la información! una pequeña observación: En mi opinion la fuente es algo dificil de leer

    ResponderEliminar
  3. Excelente trabajo el que haces ayudando a comprender el funcionamiento de algunas cosas con las que estamos en contacto a diario como programadores pero que algunas veces no las comprendemos del todo.

    Tengo poco tiempo siguiendo tu blog y viendo tus aportaciones a yii framework sobre todo el componente cruge y la verdad has hecho que nuevamente me despierte el interes por la programacion, a esto me dedico soy desarrollador pero he estado estancado ya hace bastante tiempo y creo que tu trabajo es inspirador. Mis respetos.

    ResponderEliminar