jueves, 20 de octubre de 2016

Factory Method. Parte I - Introducción

Una norma básica para evitar acoplamiento entre clases concretas y el cliente que las usa es trabajar para la interface y no para la implementación. De eso se trata: no queremos clases concretas en nuestro código.

Tenemos nuestro código, con la lógica de negocio, que utiliza distintas clases que heredan de una interface común, pero, ¿cómo hacemos para no utilizarlas directamente? Esa es la gran incógnita a resolver.

¿Por qué no queremos clases concretas en nuestro código?


Imaginemos los siguientes casos:

  • Tenemos código que queremos reutilizar en distintos proyectos ya que la parte central hace algo concreto, pero en cada proyecto, por lo que sea, ese algo concreto se hace de distintas formas. 
  •  Queremos que nuestro código central no tenga que ocuparse de cosas que no son su cometido. Dicho de otra forma, tenemos código central que para lograr su fin puede utilizar distintos caminos que no tenemos porqué conocer, queremos que el sistema sea independiente de sus productos.

En ambos casos aprovechamos clases concretas de un mismo tipo. Utilizamos polimorfismo. En estos casos la clase principal no puede saber qué productos debe crear.

Esas clases concretas se pueden seleccionar en un largo condicional dentro del código central, y eso es precisamente lo que queremos evitar. Y además, para el condicional necesitaríamos un parámetro en el código central que no haría falta para nada más.

¿Qué pasa si necesitamos crear una nueva clase concreta? ¿O nos vamos a otro proyecto con clases concretas diferentes?

Habría que modificar el código central, la lógica de negocio. Cualquier programador que meta una clase concreta nueva debería tocar esa parte, y eso no es recomendable. ¿Por qué no sacamos esa creación de clases concretas fuera de la lógica central (fuera del framework)?

Por eso no queremos acoplar la lógica de negocio a la creación de clases concretas.

Queremos que el código central se centre sólo en qué hace, trabajando contra una interface, sin conocer así a las clases concretas, y que la creación de estas esté desacoplada; si añadimos una nueva clase concreta no tendremos que tocar la parte central del código.

¿Cómo creamos entonces esas clases concretas?


Aquí entran en juego distintos patrones de diseño, y en ese caso concreto el Factory Method.

La definición del propósito de este patrón en el libro Design Patterns: Elements of reusable object-oriented software es la siguiente “Define una interfaz para crear un objeto, pero deja que sean las subclases quienes decidan qué clase instanciar. Permite que una clase delegue en sus subclases la creación de objetos”.

Aquí acaba está introducción con la necesidad de tener clases que nos ayuden a crear objetos. En sucesivos posts pondré ejemplos concretos del factory method pattern.

miércoles, 12 de octubre de 2016

Carga automática de clases. Parte II.

Tras haber visto un caso muy básico de cómo crear un atuloader en php llegamos al caso más habitual en el que tenemos una estructura de carpetas. Si organizamos el caso básico donde todo estaba en el mismo directorio podríamos tener la siguiente estructura.
[code] Directorio: Proyecto:
    Directorio: Entities
        Account.php
        Output.php
    autoload.php
    test_main_autoload.php [/code]

Parte II. Autoload: Implementación de PSR-4.


El primer pensamiento sería añadir a la función spl_auload_register del caso básico la nueva ruta para los requires, pero volveríamos al problema inicial si tenemos x directorios. Crear código que fuese comprobando en cada subdirectorio antes de pasar al siguiente si existe el fichero y si es así requerirlo no es algo muy eficiente; esto lo descartamos directamente.

Lo primero que debemos hacer, para encarar una solución óptima, es hacer que cada fichero sepa en qué directorio está. ¿Cómo? Utilizando namespaces.

¿Qué son los namespaces? .Digamos que es la forma de paquetizar ficheros en un mismo ámbito, es decir, es un espacio que creamos bajo un mismo nombre para agrupar clases, variables, funciones,… Es similar a los packages de java (sólo similar). Cada namespace puede tener el nombre que queramos poner.

En este caso pondremos a cada namespace un nombre base e iremos añadiendo la ruta dónde se encuentra cada directorio simulando una jerarquía de namespaces.

Veamos el fichero principal que está en el namespace base.

[code] namespace blog\autoload; use blog\autoload\Entities AS ent; require_once "autoload.php"; try { $account = new ent\Account(5); $output = ent\Output::getOutput(ent\Output::CONSOLE); $account->addAmount(2000); $account->payCommision(); $account->addAmount(50); $account->recoverAmount(20); $output->print($account->getTotal()); } catch (Exception $e) { echo($e->getMessage().PHP_EOL); } [/code]
Vemos que lo primero que se hace, en la primera línea, es asignar el fichero al namespace base blog\autoload (podemos poner el nombre que queramos) y a continuación utilizamos el namespace blog\autoload\Entities (base + primer directorio) y le damos un alias para acortarlo.

Ahora vamos a ver las clases Account y Output pertenecientes al namespace blog\autoload\Entities.

Clase Account:
[code] namespace blog\autoload\Entities; class Account { private $commision; private $amount; public function __Construct(int $percentage) { $this->commision = $percentage; } public function addAmount(int $amount) { assert($amount >= 0); $this->amount += $amount; } public function payCommision() { $this->amount = ( ( 100 - $this->commision ) * $this->getTotal() ) / 100 ; } public function recoverAmount(int $amount) { assert($amount >= 0); $this->amount -= $amount; } public function getTotal() : int { return $this->amount; } } [/code]
Clase Output:
[code] namespace blog\autoload\Entities; class Output { const CONSOLE = 1; const FILE = 2; private function __construct() { // Configure output } public static function getOutput(int $type) : Output { // Aquí deberíamos tener un factory method para devolver el objeto output adecuado y Outpt debería ser una clase abstracta. // Queda pendiente para otro post donde se muestre cómo generar objetos. if($type === 1) return new Output(); else throw new Exception("Wrong ouput selected."); // Si no existe un output adecuado deberíamos devolver un output base en vez de lanzar una excepción. // No tener un output adecuado no debería para la ejecución. } public function print($val) { echo("Total amount is: ".$val.PHP_EOL); } } [/code]
Vemos que ambos ficheros pertenecen al namespace blog\autoload\Entities, y es en el directorio Entities dónde se encuentran.

Hay que tener en cuenta un detalle antes de continuar; aunque el namespace contenga una ruta realmente no estamos asignando una ruta, estamos asignando un nombre que casualmente coincide con la ruta. Si hacemos que un fichero use el namespace vendor\dir1 no estaremos usando a su vez vendor\dir1\dir2 aunque el directorio dir2 esté dentro de dir1, son nombres de namespaces distintos, repito, no rutas, un namespace no contiene a otro.

Antes de ver el autoload analicemos los detalles y el patrón a seguir.

  • Podemos decir que el fichero ejecutable está en el namespace base, por lo que puede tener cualquier nombre, en este caso hemos decidido que sea blog\autoload\.
  • Cada clase debe tener el mismo nombre que el fichero que lo contiene (una clase por fichero). 
  • Cada fichero deberá pertenecer al namespace compuesto por el nombre del namespace base más las barras correspondientes y los directorios en los que está cada fichero. En este caso al estar en el directorio Entities que cuelga del directorio base su namespace es blog\autoload\Entities.
  • El nombre de las clases tiene la estructura namespace\subnamespace\..\Class. Por ejemplo la clase que llega al autoload realmente es la clase blog\autoload\Entities\Account. Es decir, su namespace más el nombre de la clase.
  • Se observa que el nombre de la clase es namespace_principal\directorio1\..\Clase, por lo que si eliminamos el namespace base, y añadimos un .php al final, tenemos exactamente dónde está el fichero guardado respecto al directorio base. Ejemplo: __DIR__.\Entities\Account.php.

Siguiendo estos puntos ya tenemos un patrón: el nombre del namespace indica dónde está cada fichero respecto a un namespace base.

Pues ya es hora de ponernos a construir un fichero autoload. ¿O no? ¿Para qué crear el autoload si ya alguien lo ha creado por nosotros?

La gente de PHP-FIG ya ha creado un autoloader que podemos utilizar. Lo podéis encontrar en su recomendación estándar 4 (PSR-4 o PHP Standard Recommendation 4).

Aquí lo hemos adaptado a nuestro ejemplo modificando las variables $prefix y $base_dir y añadiendo un echo para imprimir la clase requerida.
[code] // Implementation of PSR-4 spl_autoload_register(function ($class) { echo("Requiriendo fichero: ".$class.".php".PHP_EOL); // project-specific namespace prefix $prefix = 'blog\\autoload\\'; // base directory for the namespace prefix $base_dir = __DIR__.'/'; // does the class use the namespace prefix? $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { // no, move to the next registered autoloader return; } // get the relative class name $relative_class = substr($class, $len); // replace the namespace prefix with the base directory, replace namespace // separators with directory separators in the relative class name, append // with .php $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php'; // if the file exists, require it if (file_exists($file)) { require_once $file; } }); [/code]
Con esto ya tenemos nuestro fichero autoload.php que hemos requerido anteriormente en nuestro fichero principal.

Lo ejecutamos y obtenemos el siguiente resultado.
[code] Requiriendo fichero: blog\autoload\Entities\Account.php Requiriendo fichero: blog\autoload\Entities\Output.php Total amount is: 1930 [Finished in 0.4s] [/code]
La ejecución ha sido satisfactoria.

De esta forma, requiriendo el autoload y usando un namespace (blog\autoload\Entities), podemos autocargar todas las clases que contenga este sin tener que hacer un require por cada fichero.

Código en GitHub

domingo, 9 de octubre de 2016

Carga automática de clases. Parte I.

Es frecuente encontrar, al comienzo de ficheros php, largas listas de requires con las clases que se van a utilizar. Y en esos otros ficheros que requerimos encontramos a su vez otras listas de requerimientos. Es molesto tener que escribirlos todos, o borrarlos si movemos instanciaciones, pero tiene solución creando un cargador de clases.

Parte I. Autoload simple.



Un sencillo ejemplo en el cual todos los ficheros php están en el mismo directorio.

Tenemos el siguiente fichero php:
[code] require_once "Account.php"; require_once "Output.php"; try { $account = new Account(5); $output = Output::getOutput(output::CONSOLE); $account->addAmount(2000); $account->payCommision(); $account->addAmount(50); $account->recoverAmount(20); $output->print($account->getTotal()); } catch (Exception $e) { echo($e->getMessage().PHP_EOL); } [/code]
Vemos que utiliza 2 clases: Account y Output. Este caso es muy simple, pero si utilizáramos 50 clases deberíamos hacer un listado con 50 requires. Una locura no apta para dedos sensibles a escribir una y otra vez cosas que no aportan nada.

¿Qué no aporta nada? ¿Y qué hacemos entonces? Que si lo quito porque no quiero escribirlo me salta el siguiente error:
[code] ‘Fatal error: Class 'Account' not found’ [/code]
Pues aprovechemos la siguiente función de php: spl_autoload_register.

Vamos a crear un fichero básico (muy básico) llamado autoload_basic.php.
[code] spl_autoload_register(function ($class) { echo("Requiriendo fichero: ".$class.".php".PHP_EOL); require_once $class.".php"; }); [/code]
Si no entiendes muy bien qué ocurre, y por qué hay una función cómo parámetro, tiene que ver con las closures. ¿El qué? Es tema para otro post, pero quedémonos con el siguiente resumen: el parámetro que acepta la función spl_autoload_register es de tipo callable, es decir, lo que se le pasa es una llamada de retorno. La función que se pasa como parámetro es una closure o función anónima, y su tipo es Closure. Las funciones anónimas, es decir, los tipos Closure también se pueden pasar como un parámetro callable.

La función que se registra en spl_autoload_register es el autoload que queremos que se ejecute cada vez que se quiere instanciar una clase.

Cambiemos la función principal:
[code hl="1"] require_once "autoload_basic.php"; try { $account = new Account(5); $output = Output::getOutput(output::CONSOLE); $account->addAmount(2000); $account->payCommision(); $account->addAmount(50); $account->recoverAmount(20); $output->print($account->getTotal()); } catch (Exception $e) { echo($e->getMessage().PHP_EOL); } [/code]
Ya está, todo listo para probar.

Fijemos antes que los requires a cada fichero han desaparecido y ahora estamos haciendo un único require de autoload_basic.php.

Ejecutemos:
[code] Requiriendo fichero: Account.php Requiriendo fichero: Output.php Total amount is: 1930 [Finished in 0.1s] [/code]
Con este resultado sacamos la siguiente conclusión:
  1. El ejecutable trata de instanciar una clase.
  2. El fichero que contiene la clase no ha sido requerido, por lo que se ejecuta la función autoad que se ha registrado a través de spl_autoload_register.
  3. La variable $class es el nombre de la clase que se trata de instanciar.
  4. Dentro de la función autoload se hace el require de la clase.
  5. Ya no hay que hacer x requires en el fichero principal.

Todo perfecto y en orden, salvo que todo no puede ser siempre tan simple.

NOTA: El fichero autoload_basic.php, para este caso, podría ser de la siguiente forma:
[code] spl_autoload_register(); [/code]
Si la función spl_autoload_register no recibe ningún parámetro usa directamente como variable callable por defecto la función spl_autoload que a su vez tiene como parámetro el nombre de la clase que se instancia. He preferido añadir la closure para que tenga el mismo efecto pero que se vea qué hace.

Código en GitHub