Un ejemplo del patrón abstract factory
El patrón Abstract Factory es uno de los patrones de construcción. Básicamente, proporciona una interfaz que permite crear familias de objetos relacionados entre sí, sin especificar ( ni por lo tanto, conocer a priori ) sus clases concretas.
Voy a intentar explicarlo con un ejemplo un poco más “real”. Supongamos que quiero decorar mi casa. Puedo elegir entre dos líneas de decoración distintas ( en este caso, “Moderna”, o “Clásica” ). En principio sólo voy a comprar las puertas y una televisión.
¿Por qué implementar una factoría abstracta para resolver el problema?. Voy a intentar explicarlo sobre la marcha. En primer lugar voy a suponer que en mi ciudad hay dos tiendas de decoración, la que vende elementos decorativos clásicos, y la que vende elementos decorativos modernos. Además, supondré que las dos tiendas venden los mismos elementos decorativos ( televisores y puertas ).
Bien, si yo quiero comprar una televisión, independientemente de lo moderna o clásica que sea, voy a la tienda y digo “quiero una televisión”. Además, una televisión, sea moderna o clásica, me permite hacer las mismas cosas: encenderla, apagarla, subir y bajar el volumen, aunque no se haga de la misma forma ( en la moderna, lo hago con el mando a distancia, en la antigüa me tengo que levantar ).
Por lo tanto, ya sé que tengo que ir a la tienda, y pedir una televisión. Pero también puedo encargarle a un transportista que me traiga una televisión moderna. Yo no tengo que decirle nada más que eso, porque es el transportista es el que se encarga de pensar “vale, me ha dicho que quiere una tele moderna, por tanto, tengo que ir a la tienda donde venden las teles modernas, y pedir una tele”. Pues el transportista es la factoría abstracta. Yo, como cliente, le pido una televisión moderna, y sé que voy a obtener una televisión de plasma de 42” que puedo manejar con el mando a distancia adjunto. ¿Qué cómo la ha conseguido el transportista?: no me importa. Sólo me importa que me la traiga.
Antes de empezar a implementarlo en actionScript, una consideración teórica importante. ActionScript no implementa clases abstractas. Es cierto que se pueden simular, pero no están implementadas, por lo que yo no voy a basar la arquitectura de este patrón en una clase abstracta, sino en un interfaz.
Empecemos. Hemos dicho que tanto si voy yo en persona a la tienda, como si hablo con un transportista, lo único que tengo que hacer es decir "quiero una tele". Por lo tanto, tanto las tiendas como el transportista van a implementar un interfaz común ( si fuéramos estrictos, deberían heredar de una misma clase abstracta que implementara ese interfaz ).
El interfaz será:
import net.designnation.patterns.AbstractFactory.*
interface net.designnation.patterns.AbstractFactory.IFactoryActions
{
public function getTV( ): ITVActions;
public function getDoor( ): IDoorActions;
}
Como puede verse, hay dos acciones posibles: getTV, y getDoor ( es decir, tráeme una tele, y tráeme una puerta ).
Por tanto, las dos tiendas serán algo así:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.ModernShop implements IFactoryActions
{
function ModernShop( )
{
trace( "He elegido la tienda moderna" );
}
public function getTV( ): ITVActions
{
return new BrandNewTV( );
}
public function getDoor( ): IDoorActions
{
return new ModernDoor( );
}
}
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.ClassicShop implements IFactoryActions
{
public function ClassicShop( )
{
trace( "He elegido la tienda clásica" );
}
public function getTV( ): ITVActions
{
return new OldTV( );
}
public function getDoor( ): IDoorActions
{
return new ClassicDoor( );
}
}
Como puede verse, las dos tiendas implementan el interfaz IFactoryActions ( implementan dos métodos llamados getTV y getDoor, que devolverán respectivamente, una televisión y una puerta ).
Atención. Vamos a ver con más atención el método getTV( ).
public function getTV( ): ITVActions
{
return new OldTV( );
}
¿Instanciamos una clase OldTV pero el método devuelve un interfaz?. Cierto. Eso es lo que nos permite tratar a todas las televisiones por igual, sean modernas, antigüas, o de gas. Porque lo que a nosotros nos interesa es poderle decir a la televisión: “enciéndete”, y que ésta lo haga, como sea, pero que lo haga. Eso lo logramos haciendo que las dos clases que representan televisores implementen una interfaz común:
interface net.designnation.patterns.AbstractFactory.ITVActions
{
public function pumpUpTheVolume( );
public function shutUp( );
}
Y por tanto, la televisión moderna será:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.BrandNewTV implements ITVActions
{
function BrandNewTV( )
{
trace( "He recibido mi televisión de plasma de 42 pulgadas" );
}
public function pumpUpTheVolume( )
{
trace( "por supuesto, espera que busco el mando" );
}
public function shutUp( )
{
trace( "Presionando esta tecla, el sonido se silencia" );
}
}
Mientras que la clásica es:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.OldTV implements ITVActions
{
function OldTV( )
{
trace( "Me he comprado una tv vieja" );
}
public function pumpUpTheVolume( )
{
trace( "Como no tiene mando a distancia, me tengo que levantar a subir el volumen" );
}
public function shutUp( )
{
trace( "Como no tiene mando a distancia...." );
}
}
Lo mismo pasaría con las puertas. Sea moderna o antigua, la puerta tiene dos posibles acciones, se puede abrir, y se puede cerrar, por lo tanto las dos clases que representan a puertas implementan un interfaz común:
interface net.designnation.patterns.AbstractFactory.IDoorActions
{
public function open( );
public function close( );
}
La puerta moderna:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.ModernDoor implements IDoorActions
{
function ModernDoor( )
{
trace( "Constructor de la puerta moderna" );
}
public function open( )
{
trace( "Abriendo la puerta moderna" );
}
public function close( )
{
trace( "Cerrando la puerta moderna" );
}
}
Y la clásica:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.ClassicDoor implements IDoorActions
{
function ClassicDoor( )
{
trace( "ClassicDoor constructor" );
}
public function open( )
{
trace( "Puerta clásica abierta " );
}
public function close( )
{
trace( "Puerta clásica cerrada" );
}
}
Recapitulemos. Tenemos dos tiendas. Ambas venden puertas y televisores. La tienda clásica vende puertas y televisores clásicos, y la tienda moderna vende puertas y televisores modernos.
Bien, pues nuestro transportista sería la abstract factory:
import net.designnation.patterns.AbstractFactory.*
class net.designnation.patterns.AbstractFactory.AbstractFactory
{
public static var MODERN : Number = 1;
public static var CLASSIC : Number = 2;
public static function getFactory( shopType: Number ): IFactoryActions
{
if ( ( shopType & MODERN ) == MODERN )
{
return new ModernShop( );
}
if ( ( shopType & CLASSIC ) == CLASSIC )
{
return new ClassicShop( );
}
}
}
Esta clase implementa el mismo interfaz que las dos tiendas. Por tanto, yo le diré, "oye, quiero una tele moderna", y me olvidaré de todos los detalles del proceso. Al final, tendrá algo a lo que podré subir y bajar el volumen,…. ¿Cómo?:
import net.designnation.patterns.AbstractFactory.*
var factory: IFactoryActions = AbstractFactory.getFactory( AbstractFactory.MODERN );
var myTV: ITVActions = factory.getTV( );
myTV.pumpUpTheVolume( );
var myDoor: IDoorActions = factory.getDoor( );
myDoor.close( );
Comentarios
Hola Cesar,
Solo decirte que, como siempre, un post muy útil y un ejemplo con mucha "miga". Vuestro blog se está conviertiendo en una guía de referencia de patrones de diseño aplicados a ActionScript de visita obligada.
Gracias por compartirlo y un saludo ;)
Publicado por: Carlos Rovira | Septiembre 29, 2004 09:32 AM
Mis saludos, Cesar.
Quiero felicitarte por el excelente trabajo que aqui plasmaste.
Adios y te mando gracias desde Bolivia.
Publicado por: Oscar Rueda | Noviembre 17, 2004 09:46 AM