domingo, 13 de noviembre de 2016

Factory Method. Parte III - Variante en PHP (u otros lenguajes)

Después de ver el caso más tradicional del patrón factory method, donde cada producto concreto tenía que tener una subclase del creador asociada, vamos a ver una variante para aplicar con lenguajes como PHP.

El principal problema del factory method reside en que, si tenemos muchos subproductos, vamos a tener una gran cantidad de subclases constructoras de esos productos, además de que la clase cliente debe heredar de la clase creadora; vamos a crear una jerarquía de clases como clase padre el creador que puede tener subclases muy similares entre sí.

En el anterior caso de la clase para crear una habitación, para diferenciarlo del caso que veremos a continuación, teníamos dos tipos de subproductos en cada subclase creadora: sillas y proyector. Lo más normal es que para aplicar el factory method tengamos sólo 1 tipo de producto, ya que si hay varios tipos de producto que deben trabajar siempre de forma conjunta el patrón evolucionará a un abstract factory pattern.

Así que, para comenzar, vamos a mostrar un ejemplo muy simple en el que el cliente usa un método creador para obtener distintos tipos de notificadores (email y sftp).
[code] $data = array( 1,2,3,4,5 ); $typeNotifications = array( Notifier::EMAIL, Notifier::SFTP, Notifier::EMAIL ); foreach ($typeNotifications as $type) { $notifier = Notifier::getNotifier( $type, $data ); echo( $notifier->notify() . PHP_EOL ); } [/code]
El ejemplo es muy simple, pero para imaginar un caso más real las variable $data y $typeNotifications podrían ser objetos con toda la información, o registros de una base de datos, o lectura de un fichero, ..., pero vamos a simplificarlo para ver con más claridad qué ocurre:

Detalles que llaman la atención:
  • El método creador es un método estático de la clase Notifier, es decir, no hay clase creadora.
  • Al método creador se le pasan parámetros.
  • En el ejemplo vemos que hay 3 tipos de notificaciones: email, sftp y otra vez email.

Tras ejecutar ese código tenemos el siguiente resultado:
[code] Notifyied by email next data: 1,2,3,4,5 Notifyied by sftp next data: 1,2,3,4,5 Notifyied by email next data: 1,2,3,4,5 [Finished in 0.1s] [/code]
Se observa que hemos obtenido en total dos notificadores tipo email y uno tipo sftp, tal como hemos indicado en la variable $typeNotifications. El polimorfismo entra en acción.

Entonces, ¿Dónde está la clase creadora concreta que nos devuelve el producto concreto que tiene asociado? No hay.

Veamos la clase Notifier:
[code] abstract class Notifier { const EMAIL = "email"; const SFTP = "sftp"; protected $data = array(); final private function __construct( array $data ) { $this->data = $data; } public static function getNotifier( string $type, array $data ) : Notifier { switch ( trim( $type ) ) { case self::EMAIL: return new EMailNotifier($data); break; case self::SFTP: return new SftpNotifier($data); break; default: throw new \Exception("Error: Notifier ".$type." is not available"); break; } } public abstract function notify() : string; } [/code]
Vamos a ver los puntos más importantes de esta clase:
  • Es una clase abstracta, por tanto no puede ser instaciada: no habrá objetos de la clase Notifier.
  • El método getNotifier() es estático, se puede llamar sin tener un objeto de la clase. Este es el método creador que, como se ve, devuelve una clase concreta de Notifier.
  • El constructor es privado. De esta forma una clase cliente sólo puede obtener un tipo Notifier a través del método creador.
  • El constructor es final. Con esto evitamos que las subclases puedan sobrescribir el constructor. Volvemos a reforzar que las subclases sólo se puedan obtener a través del método creador.

Los más destacado es ver que el método creador se ha movido a la clase abstracta del producto.

Con esto ya sólo necesitamos las subclases de Notifier.

Clase EmailNotifier:
[code] class EmailNotifier extends Notifier { /** * Notify */ public function notify() : string { return "Notifyied by email next data: ".implode(",", $this->data); } } [/code]
Clase SftpNotifier:
[code] class SftpNotifier extends Notifier { /** * Notify */ public function notify() : string { return "Notifyied by sftp next data: ".implode(",", $this->data); } } [/code]
Con esta variante del patrón factory method no es necesario crear una estructura jerárquica de creadores ni necesitamos que nuestra clase cliente herede de la clase creadora.

El factory method pattern, tal y como lo vemos en este ejemplo, gana mucho aplicándolo junto a principios SOLID como la inyección de dependencias.

El código de github aquí.

sábado, 5 de noviembre de 2016

Factory Method. Parte II - Ejemplo

Tras el post sobre la importancia de sacar la creación de clases concretas de nuestro sistema toca mostrar algunos ejemplos.

Voy a comenzar por el caso más tradicional, que es sobre el que más hincapié se hace en el libro Design Patterns: Elements of reusable object-oriented software. He estado pensando qué ejemplo poner, ya que en dicho libro vienen 2, uno muy genérico sobre una aplicación y la creación de tipos de documentos, y otro más amplio escrito en C++ sobre la generación de laberintos para un juego. He elegido crear un caso intermedio entre ambos, pero recomiendo leer los ejemplos del libro.

Caso: Tenemos un emulador de realidad virtual que en un momento dado crea una habitación de entretenimiento. El sistema debe ser capaz de elegir entre una habitación para ver videos y otra para escuchar audio. Para que podamos centrarnos en el patrón de diseño he creado clases muy simples.

Implementación de Factory Method: Esto es importante entenderlo para comprender el origen de la motivación del patrón. La propia aplicación, es decir, la clase que representa la habitación (EntertaimentRoom), es la que tiene los métodos creadores y los llama ella misma junto con otros métodos.

Lo primero de todo analicemos la clase EntertaimentRoom antes de aplicar Factory Method. Aquí todo el código.


Vemos las siguientes características:
  • Tiene un método begin() que hace que comience la actividad de la habitación.
  • Hay un método llamado begin_old, con un único condicional, que hace más de una cosa, por lo que se ha refactorizado en métodos individuales.
  • Por desgracia cada método individual contiene el condicional, es un bad smell de libro.
  • La clase está llena de llamadas a constructores de clases concretas, lo que hace que el código central esté acoplado a la creación de subclases.
  • El constructor recibe el tipo de habitación que se debe crear.

En la clase Main vemos cómo se instancia y cómo se usa un objeto EntertaimentRoom. Vemos que añadir o eliminar una subclase requiere tocar mucho el código central, y eso que está muy simplificado.
[code] $room = new EntertaimentRoom(EntertaimentRoom::VIDEO); $room->begin(); [/code]
En este punto decidimos aplicar el patrón Factory Method.

Aquí todo el código.

La idea principal es que cada subclase de la clase creadora decida qué objetos debe utilizar. Como hemos dicho antes la clase EntertaimentRoom es la que genera las clases concretas, por lo tanto es la que llama a los métodos de creación; es la clase creadora.

La clase EntertaimentRoom tendrá subclases que sobrescriben los métodos creadores. EntertaimentRoom nunca sabrá qué clases concretas hay que crear, ese conocimiento estará en sus clases hijas.

Hacemos que EntertaimentRoom sea ahora una clase abstracta con las siguientes características:
  • Los métodos createPlayer(), getStartInstructions() y createChairs() son ahora abstractos. Se implementan en cada subclase.
  • El constructor ya no necesita recibir el tipo de la habitación, ya que no hay ningún condicional. Desaparece la variable de clase $type.
  • Creamos las clases AudioEntertaimentRoom y VideoEntertaimentRoom como subclases de la clase creadora.
  • En el fichero Main hacemos un cambio aprovechando las características de PhP. En un fichero json metemos el tipo de habitación, pero podría leerse de base de datos, generarse forma aleatoria o ser introducido por el usuario.

Este sería el nuevo fichero Main:
[code] $dataFile = file_get_contents(__DIR__."\config.json"); $roomConfig = json_decode($dataFile); $strClassRoom = "blog\\factory\\rooms\\".ucfirst(strtolower($roomConfig->type))."EntertaimentRoom"; $room = new $strClassRoom(); $room->prepareChairs(); $room->begin(); [/code]
Y este el json:
[code]{"type":"audio"}[/code]
Con ese JSON el fichero Main.php genera una clase AudioEntertaimentRoom que crea, gracias a los métodos de creación, objetos Player y Chair apropiados para ese tipo de habitación. Cambiando el json podríamos crear una habitación de tipo VideoEntertaimentRoom.

Ahora para añadir una nueva habitación sólo tendríamos que construir una nueva subclase de EntertaimentRoom y subclases Player y Chairs.

En versiones anteriores de PhP tenemos el problema de que no sabemos qué devuelve cada clase abstracta, por lo que habría que mirar qué puede devolver cada subclase, pero en PhP7 podemos tipar los retornos, incluso poner tipado estricto, y con ver las interfaces ya sabríamos qué debe devolver cada método. No haría falta ni tocar el código central ni mirar otras subclases.

Esta implementación tiene las siguientes características.

La clase cliente es la que tiene los métodos creadores, ella misma los usa, por lo tanto la clase cliente hereda de la clase creadora. Con la implementación realizada tendríamos una subclase de creador por cada tipo de producto.

Si la clase creadora sólo generase un subtipo de Producto podríamos no necesitar crear subclases de la clase creadora. La clase creadora sería concreta y podría incluir un método creador parametrizado que devolvería el tipo de producto.

El ejemplo realizado tiene otra variante: la clase EntertaimentRoom no es abstracta e implementa los métodos creadores. Las subclases del creador, de necesitarse, podrían sobrescribir parte del método creador de la clase padre; por ejemplo si hay dos habitaciones con el mismo objeto Player pero distinto objeto Chairs. Incluso aplicando propiedades del lenguaje utilizado, y pasando un parámetro, podríamos evitar tener una clase creadora (se verá en otro post).

Y para finalizar, ¿Qué ocurre si EntertaimentRoom ya hereda de Room? ¿Y si hay muchos objetos productos? ¿Y si para aplicar Factory Method tenemos que forzar que el cliente herede de la clase creadora? No es recomendable ya que estamos condicionando el futuro. Todas estas preguntas nos llevan a evolucionar de Factory Method a Abstract Factory Pattern, el cual puede utilizar Factory Methods como base, pero esa es otra historia que dejaremos para otro día.

Código sin Factory Method aquí.
Código con Factory Method aquí.
Código con variante de Factory Method aquí.