¿Alguna vez te preguntaste qué son los patrones de diseño? En este artículo, voy a explicar por qué los patrones de diseño son importantes y mostraré algunos ejemplos en PHP, de cuándo y por qué deben ser utilizados.
¿Qué son los patrones de diseño?
Los patrones de diseño han sido optimizados, son soluciones reutilizables a los problemas de programación que nos encontramos todos los días.
Un patrón de diseño no es una clase o una biblioteca que, simplemente, puede conectarse a nuestro sistema, es mucho más que eso. Es una plantilla que tiene que aplicarse a la situación correcta. Tampoco es específica de un lenguaje. Un buen patrón de diseño debe poder aplicarse en la mayoría -si no todos los lenguajes-, sólo limitado por las capacidades del lenguaje.
Lo más importante, cualquier patrón de diseño puede ser un arma de doble filo, si se aplican en el lugar equivocado, puede ser desastroso y crear muchos problemas. Sin embargo, implementándolo en el lugar correcto y en el momento adecuado, puede ser nuestro salvador...
Hay, básicamente, 3 tipos de patrones de diseño:
- Estructural
- Creacional
- De comportamiento
Los patrones estructurales, generalmente crean relación entre las entidades, lo que facilita que estas entidades puedan trabajar en conjunto.
Los patrones creacionales indican mecanismos de instanciación (Crea un objeto, instancia de una clase); por lo que, facilitan la creación de objetos de una manera que se adapte a la situación.
Los patrones de comportamiento son usados en la comunicación entre entidades y hacen más fácil y flexible que estas entidades puedan comunicarse.
¿Por qué debo usarlos?
Los patrones de diseño son, en principio, soluciones, bien pensadas, a problemas de programación. Muchos programadores han padecido de estos problemas antes y han utilizado estas “soluciones” para ponerles remedio. Si nos encontramos con estos problemas, ¿por qué volver a crear una solución, cuando podemos usar una solución ya probada?
Por ejemplo
Imagina que tienes la responsabilidad de crear una forma en que dos clases, que hacen cosas diferentes en función de la situación, se combinen. Estas dos clases son muy utilizadas, en diferentes sitios, por el mismo sistema; por lo que es complicado retirarlas y cambiar el código existente. Además, cambiar el código necesitaría que se verifiquen cada uno de los cambios, ya que estos tipos de ediciones y reorganizaciones, en un sistema que esta basado en diferentes componentes, casi siempre introducen nuevos errores. En vez de hacer esto, puedes implementar una variación del patrón de estrategia y uno de adaptación, los que fácilmente pueden manejar este tipo de escenarios.
<?php
class StrategyAndAdapterExampleClass {
private $_class_one;
private $_class_two;
private $_context;
public function __construct( $context ) {
$this->_context = $context;
}
public function operation1() {
if( $this->_context == "contexto_para_clase_uno" ) {
$this->_class_one->operation1_in_class_one_context();
} else ( $this->_context == "contexto_para_clase_dos" ) {
$this->_class_two->operation1_in_class_two_context();
}
}
}
?>
¿Sencillo, cierto? Ahora, echemos un vistazo al patrón de estrategia.
El patrón de estrategia (Strategy Pattern)
El patrón de estrategia es un patrón de diseño de comportamiento que nos permite decidir que curso de acción debería tener un programa, basado en un contexto específico en tiempo de ejecución. El programa encapsula dos algoritmos diferentes dentro de dos clases y decide, en tiempo de ejecución, que estrategia debe seguir.
En nuestro ejemplo, la estrategia esta basada en la variable $_context, lo que fuere en el momento en que la clase fue instanciada. Si se diera el contexto para la clase uno, usaríamos class_one, y viceversa.
Bonito, ¿pero dónde puedo aplicar esto?
Imagina que estas desarrollando una clase que puede crear o actualizar el registro de un nuevo usuario. Igual, necesitas los mismos campos (name, address, mobile number, etc.); pero, dependiendo de la situación, tienes que usar funciones diferentes para cuando creas y para cuando actualizas. Ahora, probablemente solo uses una condición if-else para conseguirlo; sin embargo, ¿que sucedería si necesitaras usar esta clase en un lugar diferente? En este caso, tendrías que reescribir la misma condición if-else otra vez. ¿No sería más fácil especificar su contexto?
<?php
class User {
public function CreateOrUpdate($name, $address, $mobile, $userid = null)
{
if( is_null($userid) ) {
// Esto significa que el usuario aún no existe, crear nuevo registro
} else {
// Esto significa que el usuario ya existe, actualizar por userid
}
}
}
?>
Ahora, el patrón de estrategia “habitual” implica encapsular sus algoritmos dentro de otra clase, pero en este caso, otra clase sería un desperdicio. Recuerda que uno no tiene que seguir el modelo exactamente. Podemos hacer variaciones al trabajo, siempre y cuando, el concepto siga siendo el mismo y solucione el problema.
El patrón adaptador (Adapter Pattern)
El patrón adaptador es un patrón de diseño estructural que nos permite reutilizar una clase con una interfaz diferente, lo que permite ser utilizada por un sistema que utiliza diferentes métodos de llamada.
Esto también nos permite cambiar algunos campos que están siendo recibidos de la clase cliente, convirtiéndolos en algo compatible con las funciones adaptadas.
¿Cómo podemos darle uso?
Otro término para hacer referencia a una clase adaptador es un “wrapper” o contenedor, lo que básicamente nos permite “envolver” (“wrapp”) acciones en una clase y reusar estas acciones en las situaciones adecuadas. Un ejemplo clásico puede ser cuando creas una clase dominio para las clases de tabla. En lugar de llamar a las diferentes clases de tabla y llamar a sus funciones una por una, podemos encapsular todos estos métodos en un método que utilice una clase adaptador. Esto no sólo nos permitirá reutilizar cualquier acción que se desee, sino también evitará que tengamos que reescribir el código si necesitamos usar la misma acción en un sitio distinto.
Comparemos estas dos clases.
Enfoque sin adaptador
<?php
$user = new User();
$user->CreateOrUpdate( //inputs );
$profile = new Profile();
$profile->CreateOrUpdate( //inputs );
?>
Si necesitamos hacer lo mismo en un lugar diferente, o incluso reusar este código en un proyecto distinto, tendríamos que reescribir todo otra vez.
Mejor
<?php
$account_domain = new Account();
$account_domain->NewAccount( //inputs );
?>
En esta situación, tenemos una clase wrapper, la cual podría ser nuestra clase dominio Account.
<?php
class Account()
{
public function NewAccount( //inputs )
{
$user = new User();
$user->CreateOrUpdate( //subset of inputs );
$profile = new Profile();
$profile->CreateOrUpdate( //subset of inputs );
}
}
?>
De esta forma, podemos usar la clase dominio Account siempre que la necesitemos, además de poder englobar otras clases bajo la clase dominio.
El patrón método de fábrica (Factory Method Pattern)
El patrón método de fábrica es un patrón de diseño creacional que hace exactamente lo siguiente: Es una clase que actúa como una fábrica de instancias de objetos.
El principal objetivo de este patrón es encapsular el procedimiento creacional que diferentes clases pueden tener, en una sólo función. Al proporcionar el contexto adecuado al método de fábrica, éste será capaz de devolver el objeto correcto.
¿Cuándo puedo usar esto?
El mejor momento para utilizar el patrón de método de fábrica es cuando tienes múltiples variantes de una sola entidad. Digamos que tienes una clase button, esta clase tiene diferentes variaciones, como ImageButton, inputButton y FlashButton. Dependiendo del lugar, es posible que deba crear diferentes botones -aquí es donde podemos utilizar una fábrica para crear los botones, en lugar tuyo!
Empecemos creando nuestras tres clases:
<?php
abstract class Button {
protected $_html;
public function getHtml()
{
return $this->_html;
}
}
class ImageButton extends Button {
protected $_html = "..."; //Este es el HTML para el botón basado en una imagen
}
class InputButton extends Button {
protected $_html = "..."; //Este es el HTML para el botón normal (<input type="button"... />);
}
class FlashButton extends Button {
protected $_html = "..."; //Esti debe ser cualquier HTML que quieras usar para el botón flash
?>
Ahora, podemos crear nuestra clase de fábrica:
<?php
class ButtonFactory
{
public static function createButton($type)
{
$baseClass = 'Button';
$targetClass = ucfirst($type).$baseClass;
if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) {
return new $targetClass;
} else {
throw new Exception("El tipo de botón '$type' no existe.");
}
}
}
?>
Podemos utilizar el código de la siguiente manera:
<?php
$buttons = array('image','input','flash');
foreach($buttons as $b) {
echo ButtonFactory::createButton($b)->getHtml()
}
?>
El resultado deberá ser el código HTML de todos los tipos de botones. De esta manera, seremos capaz de señalar qué botón crear dependiendo de la situación y poder reusar la condición, también.
El patrón decorador (Decorator Pattern)
El patrón decorador es un patrón de diseño estructural que nos permite añadir comportamientos nuevos, o adicionales, a un objeto en tiempo de ejecución, dependiendo de la situación.
El objetivo es hacerlo de tal manera que las funciones extendidas se puedan aplicar a un caso concreto y, al mismo tiempo, mantener la posibilidad de crear una instancia original que no tienen las nuevas funciones. También permite combinar múltiples decoradores a una instancia, por lo que no hay que trabajar con un decorador para cada instancia. Este patrón es una alternativa a la subclase, la cual crea una clase que hereda funcionalidad de una clase padre. A diferencia de la subclase, que añade el comportamiento en tiempo de compilación, este patrón nos permite agregar un nuevo comportamiento en tiempo de ejecución, si la situación así lo requiere.
Para implementar el patrón Decorator, debemos seguir los siguientes pasos:
- Crear una subclase de la clase original “Component” en una clase “Decorator”
- En la clase Decorator, debemos añadir un Puntero a Component como un campo
- Pasar un Component al constructor de Decorator para inicializar el puntero Component
- En la clase Decorator, redirigir todos los metodos Component al puntero Component, y
- En la clase Decorator, sobreescribir cualquier método Component cuyo comportamiento deba ser modificado
Pasos cortesía de la wikpedia.
¿Cuándo puedo usarlo?
El mejor lugar para usar el patrón Decorator es cuando tenemos una entidad que necesita tener un nuevo comportamiento sólo si la situación así lo requiere. Por ejemplo, tenemos un elemento enlace HTML, un enlace para cerrar sesión, que queremos realice cosas diferentes basados en la página en la que aparece. Para ello, podemos usar el patrón decorador.
Primero, definimos las diferentes “decoraciones” que necesitaremos.
- Si estamos en la portada como usuarios loggeados, tener el enlace dentro de etiquetas <H2>.
- Si estamos en una página distinta como usuarios loggeados, tener un enlace con subrayado.
- Si estamos loggeados, tener un enlace dentro de etiquetas <strong>.
Una vez que hemos establecido nuestras decoraciones, podemos empezar con la programación:
<?php
class HtmlLinks {
//algunos métodos disponibles en todos los enlaces html
}
class LogoutLink extends HtmlLinks {
protected $_html;
public function __construct() {
$this->_html = "<a href="logout.php">Logout</a>";
}
public function setHtml($html)
{
$this->_html = $html;
}
public function render()
{
echo $this->_html;
}
}
class LogoutLinkH2Decorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<h2>" . $this->_html . "</h2>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
class LogoutLinkUnderlineDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<u>" . $this->_html . "</u>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
class LogoutLinkStrongDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->setHtml("<strong>" . $this->_html . "</strong>");
}
public function __call( $name, $args )
{
$this->_logout_link->$name($args[0]);
}
}
?>
Ahora, debemos ser capaces de utilizarlo de esta manera:
<?php
$logout_link = new LogoutLink();
if( $is_logged_in ) {
$logout_link = new LogoutLinkStrongDecorator($logout_link);
}
if( $in_home_page ) {
$logout_link = new LogoutLinkH2Decorator($logout_link);
} else {
$logout_link = new LogoutLinkUnderlineDecorator($logout_link);
}
$logout_link->render();
?>
Aquí podemos ver la manera de combinar múltiples decoradores si los necesitamos. Dado que todos los decoradores utilizan el método mágico __call, podemos llamar a los métodos de la función original. Si asumimos que estamos en la portada y loggeados, la salida HTML deberá ser:
<strong><h2><a href="logout.php">Logout</a></h2></strong>
El patrón instancia única (Singleton Pattern)
El patrón de diseño Singleton es un patrón de diseño creacional que se asegura de tener una sola instancia de una clase particular durante su tiempo de ejecución, y proporciona un punto de acceso global a ella.
Esto permite configurar un punto de “coordinación” para otros objetos que utilicen la instancia singleton; asimismo, las variables singleton siempre serán las mismas y estarán disponibles para todos los demás objetos de la aplicación, que las llamen.
¿Cuándo puedo usarlo?
Si necesitamos pasar una instancia específica de una clase a otra, podemos usar el patrón de instancia única para evitar tener que pasar la instancia vía el constructor o un argumento. Imagina que haz creado una clase Session, que simula el array global $_SESSION.
A esta clase sólo necesitamos instanciarla una vez; entonces, podemos implementar un patrón singleton de la siguiente manera:
<?php
class Session
{
private static $instance;
public static function getInstance()
{
if( is_null(self::$instance) ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() { }
private function __clone() { }
// cualquier otro método(s) de sesión que usemos
...
...
...
}
// obtener una instancia de sesión
$session = Session::getInstance();
?>
Así pues, podemos acceder a nuestra instancia de sesión desde diferentes partes de nuestro código, incluso en diferentes clases. Esta data será persistente y se mantendrá a lo largo de todas las llamadas getInstance.
Conclusión
Hay muchos más patrones de diseño por estudiar; en este artículo, sólo hemos mencionado algunos de los más destacados y utilizados en la programación. Si estas interesado en leer sobre los demás patrones de diseño, la página de Design Patterns de la Wikipedia esta repleta de información. Si no es suficiente, pueden leer el libro “Design Patterns: Elements of Reusable Object-Oriented Software”, considerado uno de los mejores libros sobre patrones de diseño.
Una última cosa: Cuando uses estos patrones de diseño, siempre asegúrate de estar tratando de resolver el problema adecuado. Como mencioné al principio, estos patrones de diseño son una espada de doble filo: Si se utiliza en el contexto equivocado, potencialmente pueden empeorar las cosas; pero, si se utilizan correctamente, pueden ser indispensables.
Está algo complejo el asunto. A leerlo con mas calma.
:) Tal ves un poco,
un par de artículos para complementar este, son los de ibm:
Saludos
Hola Balu, yo habia comenzado a leer sobre patrones de diseño hace algunos meses, pero por trabajo no seguí, hoy di con este artículo y la verdad que me ha parecido excelente, ahora me ha quedado bastante claro, mas que nada las situaciones en la que se puede o debe usar cada patron. Te felicito por el blog, saludos!
Gracias por tu post.....me sirvio para entender la idea del Decorator pattern pero no me funciono bien tu ejemplo...asi que hice el mio:
<?php
class html {
protected $html;
function __construct($html){
$this->setHtml($html);
}
function __tostring(){
return $this->html;
}
function setHtml($html){
$this->html =$html;
}
} #
/* decorator */
abstract class tag extends html {
protected $obj;
function __construct ($obj=null){
$this->obj = $obj;
}
} #
class link extends tag {
private $uri;
function __construct ($x=null){
/* podria usar is_object() pero es inespecifico */
if ($x instanceof tag){
parent::__construct($x);
}else{
$y = new html($x);
parent::__construct($x);
return $y;
}
}
function setUri($uri){
$this->uri = $uri;
}
function linkear (){
$this->setHtml ("uri}'>{$this->obj}");
}
} #
class strong extends tag {
function render(){
$this->setHtml (''.$this->obj.'');
}
} #
class italic extends tag {
function render(){
$this->setHtml (''.$this->obj.'');
}
} #
class img extends html {
private $img;
function __construct($img){
$this->img = $img;
$this->render();
}
private function render(){
$this->setHtml ("img}' />");
}
} #
/* Creo imagen y le aplico enlace */
$i = new img ('http://2.bp.blogspot.com/-nURw-T8ErDM/ThYBg4eyhGI/AAAAAAAAB8I/Tapme43aap...');
$i = new link($i);
$i->setUri('http://google.es');
$i->linkear();
/* Creo link sobre el HTML on_the_fly */
$h = new link('Google');
$h->setUri('http://google.es');
$h->linkear();
/* Le hago un strong */
$h = new strong ($h);
$h->render();
/* Le hago italica */
$h = new italic ($h);
$h->render();
echo $i;
echo new html('');
echo $h;
Muy bueno el post. Estaba estudiandolo y me he dado cuenta que en el ejemplo patron Decorator hay algun error (o al menos eso creo). Espero que sirva de ayuda:
Las classes deben extender de LogoutLink
y en las lineas
$this->_logout_link = $logout_link;
$this->setHtml("" . $this->_html . "");
deberia de ser
$this->_logout_link = $logout_link;
$this->setHtml("" . $logout_link->_html . "");
ya que si no el valor de _html se perderia...
Saludos y gracias por esta info
Antes de ver tu commentario algo no me cuadraba en el decorator, lo tuve que escribir para ver lo que era, pero me quede con la duda de si estaba bien implementado como yo lo hice y al leer tu coment, no estaba tan mal!
Use el patrón singleton para modelar una conexión persistente de mysql para php ya que el mysql nativo de php no soporta transacciones si es que las consultas no se realizan con la misma conexion (su id de mysql_connect()) y tenia problemas al hacer desarrollar de forma modular (CAPAS) con MVC. y tambien use el patrón proxy para conectarme con el modelo.
Hola estaba checando el código de "Decorador" y leí los comentarios de los demás chicos y bien lo solucione como creo debe solucionarse de manera correcta:
En primera no puedes acceder a tipos protegidos como si fueran statics o públicos, debes de indicarle el objeto que lo contiene.
Por otro lado debes de hacer una comprobación de si esta "seteado" el indice 0 del arreglo arguments para poder llamar el método con argumentos de la clase padre, ya sea usando Parent o con call_user_func_array o mediante la comprobación.
Por otro lado veo correcto que heredes de HtmlLinks, otra cosa para poder obtener el valor de un atributo protegido olvidaste colocar un método getter.
Para finalizar en la implantación hubiera sido bueno colocar el nombre de la variable con False o Public.
Bueno aquí mi versión solo adapte y corregí ciertas cosillas y utilice en 2 métodos mágicos __call la función call_user_func_array y en otro realice una comprobación:
<?php
class HtmlLinks {
//algunos métodos disponibles en todos los enlaces html
}
class LogoutLink extends HtmlLinks {
protected $_html;
public function __construct() {
$this->_html = "Logout";
}
public function setHtml($html)
{
$this->_html = $html;
}
public function getHtml()
{
return $this->_html;
}
public function render()
{
echo $this->_html;
}
}
class LogoutLinkH2Decorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->_logout_link->setHtml("". $this->_logout_link->getHtml() ."");
}
public function __call( $name, $args )
{
if (isset($args[0])):
$this->_logout_link->$name($args[0]);
else:
$this->_logout_link->$name();
endif;
}
}
class LogoutLinkUnderlineDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->_logout_link->setHtml("" . $this->_logout_link->getHtml() . "");
}
public function __call( $name, $args )
{
return call_user_func_array(array($this->_logout_link, $name), $args);
}
}
class LogoutLinkStrongDecorator extends HtmlLinks {
protected $_logout_link;
public function __construct( $logout_link )
{
$this->_logout_link = $logout_link;
$this->_logout_link->setHtml("" . $this->_logout_link->getHtml(). "");
}
public function __call( $name, $args )
{
return call_user_func_array(array($this->_logout_link, $name), $args);
}
}
//implementacion
$logout_link = new LogoutLink();
$is_logged_in = False;
$in_home_page = True;
if( $is_logged_in ) {
$logout_link = new LogoutLinkStrongDecorator($logout_link);
}
if( $in_home_page ) {
$logout_link = new LogoutLinkH2Decorator($logout_link);
} else {
$logout_link = new LogoutLinkUnderlineDecorator($logout_link);
}
$logout_link->render();
?>