Modelo de objetos en PHP5

Herencia revisitada

Clases abstractas

Una clase se define como abstracta cuando representa una entidad que no debería ser instanciada.

En los ejemplos utilizados hasta ahora se define una clase Vehiculo, que sirve de base para las clases Coche, Moto, etc... Sin embargo, en el mundo real (dominio del problema) no se encuentran objetos de la clase Vehiculo, sino que existen coches, motos, camiones... Al final todos son vehículos, pero todos son de un tipo más concreto (vehículo es un término abstracto que agrupa a una serie de sistemas que se utilizan para el transporte).

Por lo tanto sería bastante lógico declarar la clase Vehiculo como abstracta, forzando así la necesidad de construir clases derivadas que representen elementos más concretos:

abstract class Vehiculo
{
public
$potencia;
public
$peso;
}

//// ERROR: no se puede instanciar una clase abstracta
//// $objVehiculo = new Vehiculo();


//// una Moto es un Vehiculo
class Moto extends Vehiculo
{
public
$anguloMaxInclinacionEnCurva;
}

//// creamos un objeto Moto
$obj_moto = new Moto();

Las clases abstractas se suelen utilizar como base para crear una jerarquía en la que todas las clases comparten una parte de la interfaz. Dentro de una clase abstracta se pueden definir métodos abstractos. Los métodos abstractos no tienen implementación (ni funcionalidad), simplemente definen una parte de la interfaz que deben implementar las clases derivadas (la clase base obliga a que se definan esos métodos en la clase derivada).

Por ejemplo, para la clase Vehiculo se podría definir un método abstracto aceleracionAproximada() de forma que tenga que ser implementado para cada tipo de vehículo (la aceleración depende de la masa (peso), de la potencia y de otros factores que dependen a su vez del tipo de vehículo):

abstract class Vehiculo
{
public
$potencia;
public
$peso;

function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;

}

function
relacionPesoPotencia()
{
if (
$this->potencia>0)
{
return (
$this->peso/$this->potencia);
}

return -
1;
}

//// devuelve el tiempo en alcanzar los 100 Km/h
//// partiendo de cero
//// cada tipo de vehiculo tendra una
//// aceleracion aproximada en funcion de sus
//// caracteristicas particulares
abstract function aceleracionAproximada();

}

//// una Moto es un Vehiculo
class Moto extends Vehiculo
{

function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;
}

//// devuelve el tiempo en alcanzar los 100 Km/h
//// partiendo de cero
function aceleracionAproximada()
{
$coeficienteTransmision = 3.0;

$t = $this->peso * 771.73 / (2.0 * $this->potencia * 735);
$t = $t * $coeficienteTransmision;
return
$t;
}
}


//// un Coche es un Vehiculo
class Coche extends Vehiculo
{
function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;
}

//// devuelve el tiempo en alcanzar los 100 Km/h
//// partiendo de cero
function aceleracionAproximada()
{
$coeficienteTransmision = 2.2;

if (
$this->potencia==0)
{
return -
1;
}

$t = $this->peso * 771.73 / (2 * $this->potencia * 735);
$t = $t * $coeficienteTransmision;

return
$t;
}
}

//// un coche (125CV, 1300Kg)
$coche = new Coche (125, 1300);
echo
"coche (0-100): ".$coche->aceleracionAproximada();
echo
"<br>";

//// una moto (60CV, 250Kg)
$moto = new Moto (60, 250);
echo
"moto (0-100): ".$moto->aceleracionAproximada();
echo
"<br>";

Una clase derivada de una clase abstracta puede ser abstracta a su vez. El objetivo es el mismo: servir de base común para otras clases más específicas y obligar a sus clases derivadas a que implementen una determinada funcionalidad.

Una clase derivada puede omitir la declaración de un método abstracto de su clase base, pero sólo si se declara como abstracta. En este caso, la implementación del método será responsabilidad de las clases derivadas a partir de este nivel jerárquico.

//// un Coche es un Vehiculo
//// se declara como abstracta para no tener que
//// implementar aceleracionAproximada()
abstract class Coche extends Vehiculo
{
function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;
}
}

//// un Formula1 es un Coche
class Formula1 extends Coche
{
function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;
}

//// devuelve el tiempo en alcanzar los 100 Km/h
//// partiendo de cero
function aceleracionAproximada()
{
$coeficienteTransmision = 3.5;

if (
$this->potencia==0)
{
return -
1;
}

$t = $this->peso * 771.73 / (2 * $this->potencia * 735);
$t = $t * $coeficienteTransmision;

return
$t;
}
}


//// ERROR: Coche es ahora una clase abstracta
//// un coche (125CV, 1300Kg)
//// $coche = new Coche (125, 1300);
//// echo "coche (0-100): ".$coche->aceleracionAproximada();
//// echo "<br>";

//// un formula 1 (900CV, 600Kg)
$ferrari = new Formula1 (900, 600);
echo
"ferrari (0-100): ".$ferrari->aceleracionAproximada();
echo
"<br>";

Herencia múltiple

PHP no soporta herencia múltiple (una clase derivada sólo puede tener una clase base). Sin embargo sí es posible que una misma clase implemente varios interfaces.

Interfaces

El concepto de interface es muy similar al de la clase abstracta. Se utiliza para definir una interfaz (conjunto de métodos) y forzar a que las clases derivadas implementen la funcionalidad necesaria para dar soporte a ese interfaz:

interface InterfaceVehiculo
{
public function
dimePeso();
public function
dimePotencia();

}
//// fin de la interface "Vehiculo"


class Moto implements InterfaceVehiculo
{
private
$peso;
private
$potencia;

function
__construct($potencia,$peso)
{
$this->potencia = $potencia;
$this->peso = $peso;

return
true;

}
//// fin de "__constructor"

//// implementa el metodo dimePeso()
//// declarado en la interfaz
function dimePeso()
{
return
$this->peso;
}

//// implementa el metodo dimePeso()
//// declarado en la interfaz
function dimePotencia()
{
return
$this->potencia;
}
}

Si la clase Moto del ejemplo anterior no implementara alguno de los métodos del interfaz InterfaceVehiculo aparecería un error.

La diferencia fundamental entre una clase abstracta y un interface es que la clase abstracta puede tener una implementación, una funcionalidad (como por ejemplo el método relacionPesoPotencia() de la clase abstracta Vehiculo de un ejemplo anterior), mientras que los interfaces sólo son una especie de molde para construir clases con (al menos) un conjunto específico de métodos.

Los interfaces son muy útiles para definir un mecanismo común de comunicación en objetos de tipología variada. Por ejemplo:

interface Ficha
{
public function
queEres();
}

//// clase Persona
class Persona implements Ficha
{
public function
queEres()
{
return
"Soy una persona";
}
}

//// clase Gato
class Gato implements Ficha
{
public function
queEres()
{
return
"Soy un gato";
}
}

//// clase Barco
class Barco implements Ficha
{
public function
queEres()
{
return
"Soy un barco";
}
}

En el ejemplo anterior se establece una interfaz común, que puede ser utilizada por otros objetos y funciones para comunicarse con los objetos sin necesidad de saber si son de un tipo concreto:

//// pregunta 'que eres' a todos los objetos
function curiosidad ($arrayObjetos)
{
foreach (
$arrayObjetos as $objeto)
{
echo
$objeto->queEres();
echo
"<br>";
}
}


//// creamos objetos de varios tipos
//// (todos tienen en comun el mismo interface)
$arrayObj[] = new Persona();
$arrayObj[] = new Gato();
$arrayObj[] = new Barco();

//// pasamos el array a curiosidad()
curiosidad ($arrayObj);

//// salida:
//// Soy una persona
//// Soy un gato
//// Soy un barco

Como los interfaces son simples plantillas, una clase puede implementar todos los interfaces que sea necesario.

También es posible aplicar a la vez herencia (simple) e implementación a una clase:

interface Ficha
{
public function
queEres();
}

interface
Identificacion
{
public function
quienEres();
}

//// clase Persona
class Persona
{
public
$nombre;
}


//// un Piloto es una Persona
class Piloto extends Persona implements Ficha, Identificacion
{
public function
queEres()
{
return
"Soy un piloto de carreras";
}

public function
quienEres()
{
return
"Mi nombre es: ".$this->nombre;
}

}

En ocasiones puede ser útil aplicar un interface a una clase base abstracta, con el fin de obligar a sus clases derivadas a implementarlo. Al igual que sucede con la herencia de las clases abstractas, una clase puede delegar en sus clases derivadas la implementación de un interface, pero tiene que ser declarada como abstracta.

interface Ficha
{
public function
queEres();
}

//// clase Persona
//// es abstracta porque no interesa implementar
//// la interfaz de Ficha en este nivel
abstract class Persona implements Ficha
{
public
$nombre;

abstract function
dimeNombre();
}

//// un Piloto es una Persona
class Piloto extends Persona
{

//// implementa el metodo abstracto de
//// su clase base
public function dimeNombre()
{
return
$this->nombre;
}

//// implementa la interfaz impuesta por Ficha
public function queEres()
{
return
"Soy un piloto de carreras";
}

}


$piloto = new Piloto;
$piloto->nombre = "Fernando Alonso";

echo
"nombre: ".$piloto->dimeNombre();
echo
"<br>";
echo
"¿que eres?: ".$piloto->queEres();

 

Felipe Fernández Perera : Google+