martes, 6 de agosto de 2013

PHP orientado a objetos - Clases abstractas e interfaces

Clase abstractas

La llegada de las interfaces fue uno de los grandes cambios de PHP5. Ya que con ella PHP daba un paso más hacia la programación orientada a objetos completa.
Una clase abstracta es una clase que no se puede instanciar (provocaría error) y por lo tanto no se puede crear un objeto a partir de ella. Su intención es definir una estructura (plantilla), que puede estar parcialmente implementada, para cualquier clase que quiera extenderla. Puede implementear ciertas funcionalidades y dejar que sus herederas terminen de implementearla.
Para crear una clase abstracta debemos usar la palabra clave abstract:

 abstract class Ejemplo{  
 }  

Una clase abstracta debe contener como mínimo un método abstracto. Los métodos abstractos no tienen implementación, sino que definen una funcionalidad que será implementada obligatoriamente en las clases herederas.

 abstract class Animal {   
  function presentacion() {   
   $sonido = $this->sonido();     
   return strtoupper($sonido);   
  }   
  abstract function sonido();     
 }   

Y aunque parezca que el ejemplo es erróneo, hay que tener en cuanta que hemos dicho que una clase abstracta no podrá instanciarse. Por lo tanto la clase que se intanciará es una heredera de la clase abstracta. Y como la clase heredera estará obligada a implementar los métodos abstractos (sonido()), no habrá ningún problema.

 class Gato extends Animal{  
   public function sonido() {  
     return "Miauuuuuuuu!!!";  
   }    
 }  
 $gato = new Gato();  
 echo $gato->sonido();  //Miauuuuuuuu!!!

Cuando se extienda esta clase, la clase heredera estará obligada a implementar los métodos que se han definido como abtractos y podrá, si quiere, sobreescribir los métodos normales para que tengan otro comportamiento.


Características principales de las clases abstractas

1.  Como ya hemos comentado una clase abstracta no puede instanciarse. Pero si que se puede instanciar una clase hija no abstracta.

2. Una clase abstracta debe de contar como mínimo con un método abstracto.

3. Una clase clase abstracta A puede ser extendida por una clase abstracta B. Y esta última puede implementar o no los métodos abstractos de su antecesora A. Si no fuera abstracta si que estaría obligada a implementar los métodos.

 abstract class Animalito extends Animal{  
   public function dormir(){  
     return "Está durmiendo";  
   }  
 }  

4. Si una clase C extiende de la anterior clase abstracta B (que a su vez extendía de la clase abstracta A) debe de implementar todos los métodos abstractos de B. Y además debe de implementar los métodos abstractos de A que no habían sido implementados en B.

 abstract class Animal {   
  public function presentacion() {   
   $sonido = $this->sonido();     
   return strtoupper($sonido);   
  }   
  abstract function sonido();     
 }   
 abstract class Animalito extends Animal{  
   public function dormir(){  
     return "Está durmiendo";  
   }  
   abstract function correr();  
 }  
 class Gato extends Animalito{  
   public function sonido() {  
     return "Miauuuuuuuu!!!";  
   }  
   public function correr() {  
     return "Está corriendo";  
   }    
 }  
 $gato = new Gato();  
 echo $gato->sonido() . "<br/>";  
 echo $gato->correr() . "<br/>";  
 echo $gato->presentacion() . "<br/>";  
 echo $gato->dormir() . "<br/>";  

5. Los métodos abstractos se definirán con una visibilidad. Y sus implementaciones en las clases herederas deben de tener la misma visibilidad o menos restrictiva. Vamos a ver como en caso contrario obtendríamos un error:

 abstract class Animalito extends Animal{  
   public function dormir(){  
     return "Está durmiendo";  
   }  
   abstract protected function correr();  
 }  
 class Gato extends Animalito{  
   public function sonido() {  
     return "Miauuuuuuuu!!!";  
   }  
   private function correr() {  
     return "Está corriendo";  
   }    
 }  

"Fatal error: Access level to Gato::correr() must be protected (as in class Animalito) or weaker in ..."

Lo que significa que la funcion correr() en Gato tiene que ser protected o public pero no private. Ya que private es más restrictivo.

Los demás métodos no abstractos que heredemos tendrán que tener la misma visibilidad en caso de sobreescritura.

6. Los métodos abstractos heredados pueden ser implemetados con argumentos opcionales que no estaban definidos en la clase padre. Si el nuevo argumento no es opcional, o sea que no tiene un valor por defecto, provocará un error (Fatal error: Declaration of ... must be compatible with ...).

 abstract class Animalito extends Animal{  
   public function dormir(){  
     return "Está durmiendo";  
   }  
   abstract protected function correr();  
 }  
 class Gato extends Animalito{  
   public function sonido() {  
     return "Miauuuuuuuu!!!";  
   }  
   public function correr($contento = TRUE) {  
     if(!$contento) return "Esta corriendo cabreado";
     return "Está corriendo";  
   }    
 }  


7. En PHP 5.3 y superiores se pueden definir métodos como abstract static sin que aparezca un mensaje de aviso (warning).

 abstract class Animal {   
  public function presentacion() {   
   $sonido = $this->sonido();     
   return strtoupper($sonido);   
  }   
  abstract static function sonido(); //recuerda que si no se indica visibilidad es public
 }   
 abstract class Animalito extends Animal{  
   public function dormir(){  
     return "Esta durmiendo";  
   }  
   abstract protected function correr();  
 }  
 class Gato extends Animalito{  
   public static function sonido() {  
     return "Miauuuuuuuu!!!";  
   }  
   public function correr($contento = TRUE) {  
     return "Esta corriendo";  
   }    
 }  
 echo Gato::sonido();  //Miauuuuuuuu!!!


Interfaces

Una interfaz se parece mucho a una clase abstracta en cuanto a su estructura. Pero la primera gran diferencia viene en el hecho que ninguno de los métodos que define tienen implementada su lógica. Por lo tanto estamos ante una plantilla pura que únicamente definirá funcionalidades.
Ahora ya no hablamos de clases hijas, sino de clases que implementan la interfaz. Y estas clases serán las encargadas de implementar, obligatoriamente, la funcionalidad definida por los métodos de la interfaz. .

Esta es la sintaxis de una interfaz:

 interface interface Logger{
    public function log($mensaje);
} 

Para implementar una interfaz, se utiliza el operador implements. Al contrario que con las clases abstractas en las extendiamos (concepto de herencia).

 class FileLogger implements Logger{  
   private $gestor;  
   private $ficheroLog;  
   function __construct($nombreFichero,$modoApertura = 'a'){  
     $this->ficheroLog = $nombreFichero;    
     $this->gestor = fopen($nombreFichero, $modoApertura) or die('No es posible abrir el fichero');    
   }      
   public function log($mensaje){  
     $mensaje = date("F j, Y, g:i a") . ': ' . $mensaje . "\n";  
     fwrite($this->gestor,$mensaje);  
   }  
   function __destruct(){  
     if($this->gestor){  
       fclose($this->gestor);  
     }  
   }  
 }  


Características principales de las interfaces

1. Una interfaz solo definirá los métodos. De su lógica se encargarán las clases que implementen la interfaz. Y evidentemente, al igual que una clase abstracta, no se pueden instanciar. Instanciaremos las clases que implementan una interfaz.

2. Todos los métodos declarados en una interfaz tienen que ser públicos.

3. Los métodos de la clase que implementa una interfaz tienen que tener la misma visibilidad que en la interfaz o sea públicos . Al contrario que con las clases abstractas que los métodos abstractos de las clase hijas podían tener la misma visibilidad o menos restrictiva.

4. La clase que implemente una interfaz debe definir las mismas estructuras de métodos que fueron definidos en la interfaz. Pero además puede definir sus métodos propios y atributos.

5. Mientras que en las clases abstractas se definir todo tipo de atributos (y su visibilidad), en las interfaces solo se pueden definir  constantes. Y estas serán heredadas automáticamente por las clases que la implementen.

6. Hay que tener cuidado cuando se define la interfaz y los nombres de sus métodos. Ya que un cambio en la interfaz ocasiona que haya que modificar todas las clases que implementen de esta. Habría que modificar el nombre de los métodos, ya que una interfaz obliga que todas las clases que implementen de ella tengan como mínimo los mismos métodos (además de tener que definir su lógica).

7. Las interfaces también pueden ser heredadas por medio de la palabra extends.

 interface InterfazA {  
   public function primer_metodo($nombre);  
 }  
 interface InterfazB extends interfazA {  
   public function segundo_metodo();  
 }  
 class Ejemplo implements interfazB {  
   public function primer_metodo($nombre) {  
   }  
   public function segundo_metodo() {  
   }  
 }  

En este ejemplo, como InterfazB esta heredando de InterfazA, en la clase Ejemplo tendremos que implementar los métodos de ambas interfaces. Ya que por jerarquía heredamos de InterfazA la estructura y el deber de implementar dicha estructura.

8. Mientras que una clase solo puede extender de una clase abstracta, una clase puede implementar más de una interfaz si se desea, separándolas cada una por una coma. Evidentemente dicha clase tendrá que implementar los métodos de cada una de las interfaces.

 interface InterfazA {  
   public function primer_metodo($nombre);  
 }  
 interface InterfazB {  
   public function segundo_metodo();  
 }  
 class Ejemplo2 implements InterfazA, InterfazB{  
   public function primer_metodo($nombre) {  
   }  
   public function segundo_metodo() {  
   }    
 }  

Hasta PHP 5.3.9 una clase no podía implementar dos interfaces que tuviesen nombres de funciones iguales. Producía un error. A partir de dicha versión el error no aparece. Sin embargo, una clase no puede implementar dos interfaces que compartan nombres de funciones  con diferentes parámetros. Esto si que provocaría un error.


Resumen diferencias entre interfaces y clases abstractas

Vamos a ver un resumen de las principales diferencias a nivel conceptual:

A)
- Clase abstracta debe de contener como mínimo un método abstracto (abstract), el cual solo solo se especifica el nombre y no se implementa. Los demás métodos pueden estar completamente implementados
- En una interfaz no es posible implementar métodos sino solo definir su nombre.

B)
- En clase abstracta un método abstract se puede definir public, protected o private. Las subclases que heredan de la clase padre tienen que implementar estos métodos con la misma visibilidad o una con menor restricción
- En una interfaz todos los métodos son públicos.

C)
- En una clase abstracta, a parte de los métodos, se pueden definir atributos, con su visibilidad, y constantes.
- En una interfaz, aparte de los métodos, únicamente se puede definir constantes.

D)
- Una clase puede heredar solo de una clase padre.
- Una clase puede implementar más de una interfaz.

E)
- Usando clases abstractas, una clase hija puede sobreescribir o no un método definido en la clase padre. Evidentemente si el método es abstracto si que está obligada a implementarlo y por lo tanto sobreescribirlo.
- Una clase que implementa una interfaz esta obligada a sobreescribir todos los métodos. Ya que como se ha dicho, una interfaz solo los define pero no los implementa.

Nota: Si una clase abstracta únicamente tiene métodos abstractos quiere decir que se está usando con la funcionalidad de una interfaz.


Y a grandes rasgos se podría concluir diciendo que las clases abstractas se utilizan para compartir funciones. Mientras que las interfaces se utilizan para compartir como se tiene que hacer algo y que tiene que tener como mínimo.


Entradas relacionadas

PHP orientado a objetos - Introducción
PHP orientado a objetos - Herencia

4 comentarios:

  1. Tus artículos sobre php me parecen MUY interesantes y didácticos.

    Por esta razón y a pesar que no suelo comentar normalmente, te hago una pequeña puntualización:

    En el segundo párrafo dices "Una clase abstracta debe contener como mínimo un método abstracto." y exactamente seria "... cualquier clase que contiene al menos un método abstracto debe ser definida como tal ..." (de http://php.net/manual/es/language.oop5.abstract.php).

    Por tanto, una clase abstracta puede NO tener ningún método abstracto (seria realmente como una clase normal pero no instanciable).
    Pero si defines un método abstracto en una clase, entonces SI debes definir la clase que lo contiene como abstracta (y entonces tiene ya el sentido y los usos que describes).

    ResponderEliminar
  2. Gracias! Era lo que estaba buscando!!

    ResponderEliminar