Programacion Orientada a Objetos III: A ver, clase…
Todos juntos, por favor:
"Preguntar por la clase de un objeto está mal".
¡Muy bien! Y la Programación Orientada a Objetos, ¿que tiene de especial?
"La POO es otra forma de hacer las cosas. No tiene nada raro"
Exacto. Nadie se va a ganar un premio Nobel por hacer un programa en Objetos. De hecho la POO es bastante aburrida, programar es una tarea tediosa y se necesita mucho esfuerzo y dedicación, para a fin de cuentas hacer algunos bits locos en la memoria de la PC. Los pibes de GoF se hicieron famosos, pero no, no hubo un Nobel.
Lo interesante está en lo que podemos resolver o crear, haciendo un programa. Al igual que un martillo o un destornillador, la POO es una herramienta.
Ya les hablé de Poo, Más Poo, Double Dispatching.
Hoy les voy a contar de dos patrones de diseño: State y Factory.
¿Se acuerdan de las bananas? ¿ Que pasa si queremos guardar más información acerca de ellas ? Por ejemplo, si la banana está verde, podrida o si está madura. Esto lo vamos a usar para saber si la banana se puede comer. Solo si la banana esta madura podemos comerla.
Un patrón de diseño es una receta que nos dice qué clases tenemos que crear, para qué vamos a usar cada una de ellas, y cómo es la forma en que esas clases interactúan entre si.
State es un patrón de diseño en Objetos que nos permite modelar estados. En una banana, el estado nos sirve para saber si está podrida, verde o madura. En una videocasetera, usamos el estado para para poder si está apagada, prendida y detenida, reproduciendo, rebobinando, grabando.
¿Para qué necesitamos saber el estado? En el caso de una banana, para saber si podemos comerla. Yo no quiero comer una banana podrida. En el caso de la videocasetera, si el aparato está apagado no se puede hacer ninguna operacion sobre el, por ejemplo: reproducir una pelicula. En general, el estado nos sirve para poder saber si podemos realizar alguna operacion, o no, que dependa de ese estado del objeto.
Primero les voy a mostrar la forma clásica de modelar estados: usando un switch.
-
class Banana
-
{
-
private $estado = "";
-
-
public function __construct( $estado ) {
-
$this->cambiarEstado( $estado );
-
}
-
-
function cambiarEstado($estado) {
-
$this->estado = $estado;
-
}
-
-
function puedoComer() {
-
switch ($this->estado) {
-
case "verde":
-
return false;
-
-
case "madura":
-
return true;
-
-
case "podrida":
-
return false;
-
-
default:
-
return false;
-
}
-
}
-
}
-
-
$b = new Banana("verde");
-
-
if ( ! $b->puedoComer() )
-
echo "No puedo comer la banana";
-
else
-
echo "Que rico!";
Como siempre, PHP. Muy simple, ¿no?
Cuando creamos una nueva banana, le indicamos el estado. En este ejemplo, acabamos de crear una banana verde, y cuando se ejecute el if de abajo, va a mostrar "No puedo comer la banana". La banana esta verde, y no se puede comer. Perfecto!
La version alternativa es usar el patron State, haciendo una clase por cada estado:
-
interface IEstadoBanana
-
{
-
function puedoComer();
-
}
-
-
class EstadoBananaVerde
-
implements IEstadoBanana
-
{
-
public function puedoComer() {
-
return false;
-
}
-
}
-
-
class EstadoBananaMadura
-
implements IEstadoBanana
-
{
-
public function puedoComer() {
-
return true;
-
}
-
}
-
-
class EstadoBananaPodrida
-
implements IEstadoBanana
-
{
-
public function puedoComer() {
-
return false;
-
}
-
}
-
-
class FabricaEstadoBanana
-
{
-
{
-
switch ($nombre) {
-
case "verde":
-
return new EstadoBananaVerde();
-
-
case "madura":
-
return new EstadoBananaMadura();
-
-
case "podrida":
-
return new EstadoBananaPodrida();
-
-
default:
-
return null;
-
}
-
}
-
}
-
-
class Banana
-
{
-
private $estado = "";
-
-
public function __construct( $estado ) {
-
$this->cambiarEstado( $estado );
-
}
-
-
function cambiarEstado($estado) {
-
$this->estado =
-
FabricaEstadoBanana::CrearEstado($estado);
-
}
-
-
function puedoComer() {
-
return $this->estado->puedoComer();
-
}
-
}
-
-
$b = new Banana("verde");
-
-
if ( ! $b->puedoComer() )
-
echo "No puedo comer la banana";
-
else
-
echo "Que rico!";
Las Interfaces nos sirven para que PHP se encargue de controlar que implementamos todos los métodos necesarios, y no nos olvidemos de ningun en nuestras clases. Es el equivalente a una clase abstracta de la que no se pueden crear objetos directamente del tipo IEstadoBanana.
Luego tenemos tres clases: EstadoBananaVerde, EstadoBananaMadura y EstadoBananaPodrida. Cada una nos sirve para modelar el estado y poder verificar si se puede comer esa banana. Cada una de esas clases implementa el metodo puedoComer, que gracias al polimorfismo nos indica si se puede comer o no la banana en base a dicho estado.
Fíjense que solamente la clase Banana conoce a su estado. Cualquier interacción con las clases de estado se realiza desde dentro de la clase Banana, sin poder accederlo desde afuera. Esto es asi por un problema de diseño donde solo la clase banana conoce su estado. Si queremos hacer algo con el estado de la banana tenemos que preguntarle a la banana, y a nadie mas.
Por último tenemos la clase FabricaEstadoBanana, que es una clase cuya única utilidad es la de crear instancias de otras clases. En este caso, se encarga de crear objetos para indicar el estado de la banana en base al nombre de ese estado. Aquí tenemos el segundo patrón, la Factory. Fíjense que la funcion CrearEstado es static, y en ningun momento creamos un objeto FabricaEstadoBanana.
Técnicamente hablando, el patron Factory que aquí utilicé es una versión reducida y mas simple de los patrones de creacion del GoF.
GoF quiere decir "Gang Of Four". Es el nombre que se les dió a un grupo de 4 personas que hace mas de 10 años escribieron un libro con 23 patrones de diseño para usar en programas orientados a objetos. El nombre del libro es "Patrones de Diseño" de Erich Gamma (y otros tres). Es un libro muy útil e interesante que todos deberian leer.
Diseñar bien en objetos no es una ciencia exacta. No esta ni bien ni mal usar o no patrones de diseño; por lo general cuando alguien usa patrones de diseño apropiadamente, se dice que está haciendo un diseño elegante o prolijo. No existe una regla general para decidir cuando usarlos o no. Hay ocasiones en las que es preferible no utilizarlos, por ejemplo cuando la eficiencia del programa es un punto importante.
En el caso de los estados, cuando tenemos muchos estados y muchas acciones para verificar, quizas sea mas prolijo hacer el programa usando el patron State. Cuando tenemos un programa mas simple con un par de estados y un par de acciones, va a ser mucho mas simple no usar el pattern.
La idea basica es que, si hay que agregar algo nuevo, no haya que sacar o modificar cosas. Simplemente agregar. Es muy tonto lo que digo, pero en el caso del State, eso responde perfectamente: cuando hay que agregar un nuevo estado lo unico que hay que hacer es crear una nueva clase. Si nos olvidamos de alguna operacion, las interfaces están para indicarnos que nos faltó poner.
Saludos!
Alejo.
April 9th, 2007 at 23:52
Interesante el tutorial. No hay alguna forma de crear un estado sin ese switch?. Supongo que en Smalltalk se podrá pasar la clase. Después busco en el libro.
Saludos!
April 10th, 2007 at 0:04
Si, se puede hacer algo asi:
[php]
function CrearEstado($nombre)
{
$clase = “EstadoBanana” . ucfirst(strtolower($nombre));
if ( class_exists( $clase ) )
return new $clase();
else
return null;
}
[/php]
Pero es bastante criptico, muy especifico de php.
April 10th, 2007 at 6:21
Como bien dijo Diego, un interesante tutorial.
Con respecto a la pregunta que hiciste en mi blog, soy administrador y líder del proyecto Tierras del más allá (www.lomasao.com para hacer un chivo ^^). Es muy duro tener que tomar decisiones, y mucho mas cuando la persona que contaba con tu confianza es tu amigo, y termina fallandote.
Hace unos minutos terminé de ver los 4 videos sobre la conferencia que dieron en San Luis, puesto en YouTube. Me llamó mucho la atención, ver que Pablo y vos son dos extremos opuestos. Pablo hablando muy técnicamente, y vos diciendo “El pelado ese”. Te juro que casi me muero de la risa ^^
Desde ya, te agradezco el comentario puesto en mi blog :)
Saludos.
Alejandro.
September 26th, 2007 at 21:48
en un juego de ajedrez como se definirian las clases abstractas?