Los patrones de diseño son sólo para arquitectos de Java; al menos eso es lo que siempre has pensado. De hecho, los patrones de diseño son útiles para todos. Si estas herramientas no son dominio exclusivo de los “astronautas arquitectónicos”, ¿qué son entonces? ¿Por qué son útiles en aplicaciones PHP? Este artículo explica estos problemas.
El libro Design Patterns presentó patrones de diseño a la comunidad de software. Los autores del libro son Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides Design (comúnmente conocidos como la "Banda de los Cuatro"). Los conceptos centrales detrás de los patrones de diseño presentados son muy simples. Después de años de practicar el desarrollo de software, Gamma y otros han descubierto ciertos patrones con diseños fijos, muy parecidos a los arquitectos que diseñan casas y edificios, desarrollando plantillas sobre dónde debería estar un baño o cómo debería construirse una cocina. Usar estas plantillas, o patrones de diseño, significa diseñar mejores edificios más rápido. El mismo concepto se aplica al software.
Los patrones de diseño no sólo representan una forma útil de desarrollar software robusto más rápidamente, sino que también proporcionan una manera de encapsular grandes ideas en términos amigables. Por ejemplo, podría decir que está escribiendo un sistema de mensajería que proporciona un acoplamiento flexible, o podría decir que está escribiendo un patrón denominado Observador.
Demostrar el valor de los patrones con ejemplos más pequeños es muy difícil. Esto a menudo parece excesivo, ya que los patrones en realidad funcionan en bases de código grandes. Este artículo no muestra una aplicación grande, por lo que debe pensar en formas de aplicar los principios del ejemplo en su propia aplicación grande, no en el código en sí que se muestra en este artículo. Esto no quiere decir que no debas usar patrones en aplicaciones pequeñas. Muchas buenas aplicaciones comienzan como aplicaciones pequeñas y progresan hasta convertirse en aplicaciones grandes, por lo que no hay razón para no desarrollar este tipo de prácticas de codificación sólidas. Ahora que comprende los patrones de diseño y por qué son útiles, echemos un vistazo a los cinco patrones comúnmente utilizados en PHP V5.
Patrón de fábrica
Originalmente en el libro Design Patterns, muchos patrones de diseño fomentan el uso de acoplamientos flexibles. Para entender este concepto, es mejor hablar del arduo viaje que realizan muchos desarrolladores trabajando en sistemas grandes. Cuando cambia una parte del código, pueden ocurrir problemas y pueden ocurrir interrupciones en cascada en otras partes del sistema, partes que alguna vez pensó que no tenían ninguna relación.
El problema es el acoplamiento estrecho. Las funciones y clases de una parte del sistema dependen en gran medida del comportamiento y la estructura de las funciones y clases de otras partes del sistema. Quiere un conjunto de patrones que permitan que estas clases se comuniquen entre sí, pero no quiere unirlas estrechamente para evitar que se entrelazan. En sistemas grandes, mucho código depende de unas pocas clases clave. Pueden surgir dificultades cuando sea necesario cambiar estas clases. Por ejemplo, suponga que tiene una clase de Usuario que lee un archivo. Desea cambiarlo a una clase diferente que lea desde la base de datos; sin embargo, todo su código hace referencia a la clase original que lee desde el archivo. En este momento, será muy conveniente utilizar el modo de fábrica.
El patrón de fábrica es una clase que tiene ciertos métodos que crean objetos por usted. Puede usar clases de fábrica para crear objetos sin usar new directamente. De esta forma, si deseas cambiar el tipo de objeto creado, solo necesitas cambiar la fábrica. Todo el código que utiliza esta fábrica se cambia automáticamente.
El Listado 1 muestra un ejemplo de una clase de fábrica. El lado del servidor de la ecuación consta de dos partes: una base de datos y un conjunto de páginas PHP que le permiten agregar comentarios, solicitar una lista de comentarios y obtener artículos relacionados con un comentario específico.
Listado 1. Factory1.php
<?php interfaz IUser { función obtenerNombre(); }
clase Usuario implementa IUser { función pública __construct( $id ) { }
función pública getName() { devolver "Jack"; } }
clase UserFactory { función estática pública Crear ($ id) { devolver nuevo usuario ($id); } }
$uo = UserFactory::Crear( 1 ); echo( $uo->getName()."n" ); ?> |
La interfaz IUser define qué acciones debe realizar un objeto de usuario. La implementación de IUser se llama Usuario y la clase de fábrica UserFactory crea objetos IUser. Esta relación se puede representar mediante UML en la Figura 1.
Figura 1. Clase de fábrica y su interfaz IUser y clase de usuario relacionadas |
Si ejecuta este código en la línea de comando usando el intérprete php, obtendrá los siguientes resultados:
%phpfactory1.php Jacobo % |
El código de prueba solicitará el objeto Usuario de fábrica y generará el resultado del método getName.
Existe una variante del patrón de fábrica que utiliza métodos de fábrica. Estos métodos públicos estáticos en una clase construyen objetos de ese tipo. Este método es útil si es importante crear objetos de este tipo. Por ejemplo, supongamos que necesita crear un objeto y luego establecer una serie de propiedades. Esta versión del patrón de fábrica encapsula el proceso en una única ubicación, por lo que no es necesario copiar un código de inicialización complejo y pegarlo en todo el código base. El Listado 2 muestra un ejemplo del uso de un método de fábrica.
Listado 2. Factory2.php
<?php interfaz IUser { función obtenerNombre(); }
clase Usuario implementa IUser { Función estática pública Carga ($id) { devolver nuevo usuario ($id); }
función estática pública Crear () { devolver nuevo usuario (nulo); }
función pública __construct( $id ) { }
función pública getName() { devolver "Jack"; } }
$uo = Usuario::Cargar( 1 ); echo( $uo->getName()."n" ); ?> |
Este código es mucho más simple. Tiene solo una interfaz IUser y una clase de Usuario que implementa esta interfaz. La clase Usuario tiene dos métodos estáticos para crear objetos. Esta relación se puede representar mediante UML en la Figura 2.
Figura 2. Interfaz IUser y clase de usuario con método de fábrica |
La ejecución del script en la línea de comando produce los mismos resultados que el Listado 1, como sigue:
%phpfactory2.php Jacobo % |
Como se mencionó anteriormente, a veces estos modos pueden parecer excesivos en entornos más pequeños. Sin embargo, es mejor aprender esta sólida forma de codificación que puede aplicar a proyectos de cualquier tamaño.
Modo de elemento único
Algunos recursos de la aplicación son exclusivos porque solo existe un recurso de este tipo. Por ejemplo, las conexiones a una base de datos a través de un identificador de base de datos son exclusivas. Desea compartir el identificador de la base de datos en su aplicación porque supone una sobrecarga al mantener la conexión abierta o cerrada, más aún durante el proceso de buscar una sola página.
El modo de elemento único satisface este requisito. Si la aplicación contiene uno y sólo un objeto a la vez, entonces este objeto es un singleton. El código del Listado 3 muestra un elemento único de conexión de base de datos en PHP V5.
Listado 3. Singleton.php
<?php require_once("DB.php");
clase Conexión de base de datos { función estática pública get() { estático $db = nulo; si ($db == nulo) $db = nueva conexión de base de datos(); devolver $db; }
privado $_handle = nulo; función privada __construct() { $dsn = 'mysql://root:contraseña@localhost/fotos'; $this->_handle =& DB::Connect( $dsn, matriz() ); }
identificador de función pública() { devolver $this->_handle; } }
print( "Manejar = ".DatabaseConnection::get()->manejar()."n" ); print( "Manejar = ".DatabaseConnection::get()->manejar()."n" ); ?> |
Este código muestra una única clase denominada DatabaseConnection. No puede crear su propia DatabaseConnection porque el constructor es privado. Pero al utilizar el método get estático, puede obtener y solo obtener un objeto DatabaseConnection. El UML para este código se muestra en la Figura 3.
Figura 3. Elemento único de conexión a la base de datos |
La mejor prueba es que el identificador de la base de datos devuelto por el método handle es el mismo entre las dos llamadas. Puede ejecutar el código en la línea de comando para observar esto.
%php singleton.php Identificador = ID de objeto #3 Identificador = ID de objeto #3 % |
Los dos identificadores devueltos son el mismo objeto. Si utiliza un elemento único de conexión de base de datos en toda su aplicación, puede reutilizar el mismo identificador en todas partes.
Puede utilizar variables globales para almacenar identificadores de bases de datos; sin embargo, este enfoque solo es adecuado para aplicaciones más pequeñas. En aplicaciones más grandes, evite el uso de variables globales y utilice objetos y métodos para acceder a los recursos. Patrón de observador
El patrón Observer le ofrece otra forma de evitar un acoplamiento estrecho entre componentes. El patrón es muy simple: un objeto se hace observable agregando un método que permite que otro objeto, el observador, se registre. Cuando un objeto observable cambia, envía mensajes a los observadores registrados. Estos observadores utilizan esta información para realizar operaciones independientes del objeto observable. El resultado es que los objetos pueden hablar entre sí sin tener que entender por qué. Un ejemplo sencillo es una lista de usuarios del sistema. El código del Listado 4 muestra una lista de usuarios y, cuando se agrega un usuario, envía un mensaje. Esta lista se puede observar a través de un observador de registros que envía mensajes cuando se agrega un usuario.
Listado 4. Observer.php
<?php interfaz IObserver { función onChanged ($ remitente, $ argumentos); }
interfaz IObservable { función agregarObservador ($observador); }
clase UserList implementa IObservable { privado $_observadores = matriz();
función pública agregarCliente( $nombre ) { foreach( $this->_observers como $obs ) $obs->onChanged( $esto, $nombre ); }
función pública addObserver($observador) { $this->_observers []= $observador; } }
la clase UserListLogger implementa IObserver { función pública onChanged($remitente,$args) { echo( "'$args' agregado a la lista de usuariosn" ); } }
$ul = nueva Lista de Usuarios(); $ul->addObserver( nuevo UserListLogger() ); $ul->addCliente( "Jack" ); ?> |
Este código define cuatro elementos: dos interfaces y dos clases. La interfaz IObservable define objetos que se pueden observar y UserList implementa esta interfaz para registrarse como observable. La lista IObserver define cómo convertirse en observador. UserListLogger implementa la interfaz IObserver. Estos elementos se muestran en UML en la Figura 4.
Figura 4. Lista de usuarios observables y registrador de eventos de lista de usuarios |
Si lo ejecuta desde la línea de comando, verá el siguiente resultado:
% php observador.php 'Jack' agregado a la lista de usuarios % |
El código de prueba crea una UserList y le agrega el observador UserListLogger. Luego agregue un consumidor y notifique a UserListLogger de este cambio.
Es fundamental darse cuenta de que UserList no sabe qué hará el registrador. Puede haber uno o más oyentes que realicen otras operaciones. Por ejemplo, podría tener un observador que envíe un mensaje a nuevos usuarios, dándoles la bienvenida al sistema. El valor de este enfoque es que UserList ignora todos los objetos que dependen de él y se centra principalmente en mantener la lista de usuarios y enviar mensajes cuando la lista cambia.
Este patrón no se limita a los objetos en la memoria. Es la base de los sistemas de consulta de mensajes basados en bases de datos que se utilizan en aplicaciones más grandes. modo cadena de mando
El patrón de cadena de mando se basa en temas poco acoplados, enviando mensajes, comandos, solicitudes o cualquier otra cosa a través de un conjunto de controladores. Cada manejador hace su propio juicio sobre si puede manejar la solicitud. Si puede, la solicitud se procesa y el proceso se detiene. Puede agregar o eliminar controladores del sistema sin afectar a otros controladores. El Listado 5 muestra un ejemplo de este patrón.
Listado 5. Chain.php
<?php interfazIComando { función onCommand( $nombre, $argumentos ); }
clase CommandChain { privado $_commands = matriz();
función pública addCommand ($ cmd) { $this->_commands []= $cmd; }
función pública runCommand( $nombre, $args ) { foreach( $this->_commands como $cmd ) { si ( $cmd->onCommand( $nombre, $args ) ) devolver; } } }
la clase UserCommand implementa ICommand { función pública onCommand( $nombre, $args ) { si ($nombre! = 'addUser') devuelve falso; echo( "UserCommand maneja 'addUser'n"); devolver verdadero; } }
clase MailCommand implementa ICommand { función pública onCommand( $nombre, $args ) { si ($nombre! = 'correo') devuelve falso; echo( "MailCommand maneja 'correo'n" ); devolver verdadero; } }
$cc = nueva Cadena de Comando(); $cc->addCommand( nuevo UserCommand() ); $cc->addCommand( nuevo MailCommand() ); $cc->runCommand( 'agregarUsuario', nulo ); $cc->runCommand( 'correo', nulo ); ?> |
Este código define la clase CommandChain que mantiene una lista de objetos ICommand. Ambas clases pueden implementar la interfaz ICommand: una que responde a solicitudes de correo y la otra que responde a la adición de usuarios. La Figura 5 muestra el UML.
Figura 5. Cadena de comandos y sus comandos relacionados |
Si ejecuta un script que contiene algún código de prueba, obtendrá el siguiente resultado:
% php cadena.php Manejo de UserCommand 'addUser' MailCommand maneja 'correo' % |
El código primero crea el objeto CommandChain y le agrega dos instancias del objeto de comando. Luego ejecute dos comandos para ver quién respondió a los comandos. Si el nombre del comando coincide con UserCommand o MailCommand, el código falla y no se produce ninguna acción. El patrón de cadena de mando es valioso a la hora de crear una arquitectura escalable para gestionar solicitudes y muchos problemas se pueden resolver con él. patrón de estrategia
El último patrón de diseño que cubrimos es el patrón de estrategia. En este patrón, los algoritmos se extraen de clases complejas y, por lo tanto, pueden reemplazarse fácilmente. Por ejemplo, si desea cambiar la forma en que se clasifican las páginas en los motores de búsqueda, el modo Estrategia es una buena opción. Piense en las partes de un motor de búsqueda: una que recorre las páginas, otra que clasifica cada página y otra que ordena los resultados según la clasificación. En ejemplos complejos, todas estas partes pertenecen a la misma clase. Al utilizar el patrón Estrategia, puede colocar la parte de disposición en otra clase para cambiar la forma en que está organizada la página sin afectar el resto del código del motor de búsqueda.
Como ejemplo más simple, el Listado 6 muestra una clase de lista de usuarios que proporciona una manera de encontrar un conjunto de usuarios en función de un conjunto de políticas plug-and-play.
Listado 6. Estrategia.php
<?php interfaz IStrategia { filtro de función ($registro); }
la clase FindAfterStrategy implementa IStrategy { privado $_nombre;
función pública __construct( $nombre ) { $this->_name = $nombre; }
filtro de función pública ($ registro) { return strcmp( $this->_name, $record ) <= 0; } }
la clase RandomStrategy implementa IStrategy { filtro de función pública ($ registro) { devolver rand( 0, 1 ) >= 0,5; } }
clase Lista de usuarios { privado $_list = matriz();
función pública __construct ($ nombres) { si ($nombres! = nulo) { foreach($nombres como $nombre) { $this->_list []= $nombre; } } }
función pública agregar ($ nombre) { $this->_list []= $nombre; }
función pública buscar ($filtro) { $recs = matriz(); foreach( $this->_list como $usuario ) { si ($filtro->filtro($usuario)) $recs []= $usuario; } devolver $recs; } }
$ul = nueva Lista de Usuarios( matriz( "Andy", "Jack", "Lori", "Megan" ) ); $f1 = $ul->find( nueva FindAfterStrategy( "J" ) ); print_r ($f1);
$f2 = $ul->find( nueva EstrategiaAleatoria() ); print_r ($f2); ?> |
Figura 6. Lista de usuarios y política utilizada para seleccionar usuarios |
La clase UserList envuelve una matriz de nombres. Implementa el método de búsqueda, que utiliza una de varias estrategias para seleccionar un subconjunto de estos nombres. Estas estrategias están definidas por la interfaz IStrategy, que tiene dos implementaciones: una que selecciona aleatoriamente al usuario y otra que selecciona todos los nombres después del nombre especificado. Cuando ejecuta el código de prueba, obtiene el siguiente resultado:
%php estrategia.php Formación ( [0] => Gato [1] =>Lori [2] =>Megan ) Formación ( [0] => Andy [1] =>Megan ) % |
El código de prueba ejecuta la misma lista de usuarios para ambas estrategias y muestra los resultados. En el primer caso, la estrategia busca cualquier nombre que siga a J, por lo que obtendrías a Jack, Lori y Megan. La segunda estrategia elige nombres al azar, produciendo resultados diferentes cada vez. En este caso, los resultados son Andy y Megan.
El patrón Estrategia es ideal para sistemas complejos de gestión de datos o sistemas de procesamiento de datos que requieren un alto grado de flexibilidad en la forma en que se filtran, buscan o procesan los datos.
Conclusión
Este artículo presenta sólo algunos de los patrones de diseño más comunes utilizados en aplicaciones PHP. En el libro Design Patterns se muestran más patrones de diseño. No dejes que la mística de la arquitectura te desanime. Los patrones son una idea maravillosa que funciona en cualquier lenguaje de programación y en cualquier nivel de habilidad.