1. Code
  2. Coding Fundamentals
  3. Design Patterns

Una guía para principiantes sobre patrones de diseño

Scroll to top

Spanish (Español) translation by Andrea Jiménez (you can also view the original English article)

¿Alguna vez te has preguntado qué son los patrones de diseño? En este artículo, explicaré por qué los patrones de diseño son importantes, y proporcionaré algunos ejemplos, en PHP, de cuándo y por qué deben usarse.


¿Qué son los patrones de diseño?

Los patrones de diseño son soluciones optimizadas y reutilizables para los problemas de programación que encontramos todos los días. Un patrón de diseño no es una clase o una biblioteca que podamos simplemente conectar a nuestro sistema; es mucho más que eso. Es una plantilla que debe implementarse en la situación correcta. Tampoco es específico del idioma. Un buen patrón de diseño debe implementarse en la mayoría de los idiomas, si no en todos, dependiendo de las capacidades del idioma. Lo más importante es que cualquier patrón de diseño puede ser un arma de doble filo: si se implementa en el lugar equivocado, puede ser desastroso y crearte muchos problemas. Sin embargo, implementándolo en el lugar correcto, en el momento adecuado, puede ser tu salvador.

Hay tres tipos básicos de patrones de diseño:

  • Estructural
  • De creación
  • De comportamiento

Los patrones estructurales generalmente se ocupan de las relaciones entre entidades, lo que facilita que estas entidades trabajen juntas.

Los patrones de creación proporcionan mecanismos de creación de instancias, lo que facilita la creación de objetos de una manera que se adapte a la situación.

Los patrones de comportamiento se utilizan en las comunicaciones entre entidades y facilitan y flexibilizan la comunicación de estas entidades.

¿Por qué deberíamos usarlos?

Los patrones de diseño son, por principio, soluciones bien pensadas para los problemas de programación. Muchos programadores se han encontrado con estos problemas antes, y han utilizado estas "soluciones" para remediarlos. Si encuentras estos problemas, ¿por qué recrear una solución cuando puedes usar una respuesta ya probada?

Ejemplo

Imaginemos que te dieron la responsabilidad de crear una manera de fusionar dos clases que hacen dos cosas diferentes según la situación. Estas dos clases son muy utilizadas por el sistema existente en diferentes lugares, lo que dificulta eliminar estas dos clases y cambiar el código existente. Para agregar a esto, cambiar el código existente requiere que también tendrás que probar cualquier código modificado, ya que este tipo de ediciones, en un sistema que se basa en diferentes componentes, casi siempre introduce nuevos errores. En lugar de hacer esto, puedes implementar una variación del patrón de estrategia y patrón de adaptador, que puede manejar fácilmente este tipo de escenarios.

1
<?php
2
class StrategyAndAdapterExampleClass {
3
	private $_class_one;
4
	private $_class_two;
5
	private $_context;
6
	
7
	public function __construct( $context ) {
8
			$this->_context = $context;
9
	}
10
	
11
	public function operation1() {
12
		if( $this->_context == "context_for_class_one" ) {
13
			$this->_class_one->operation1_in_class_one_context();
14
		} else ( $this->_context == "context_for_class_two" ) {
15
			$this->_class_two->operation1_in_class_two_context();
16
		}
17
	}
18
}

Bastante fácil, ¿verdad? Ahora, veamos más de cerca al patrón de estrategia.


Patrón de estrategia

El patrón de estrategia es un patrón de diseño de comportamiento que te permite decidir qué curso de acción debe realizar un programa, en función de un contexto específico durante el tiempo de ejecución. Encapsulas dos algoritmos diferentes dentro de dos clases y decides en tiempo de ejecución qué estrategia quieres seguir.

En nuestro ejemplo anterior, la estrategia se basa en cualquiera que fuera la variable $context en el momento en que se puso en instancia la clase. Si le das el contexto para class_one, utilizará class_one, y viceversa.

Muy bonito, ¿pero dónde puedo usarlo?

Imagina que actualmente estás desarrollando una clase que puede actualizar o crear un nuevo registro de usuario. Todavía necesita las mismas entradas (nombre, dirección, número de teléfono móvil, entre otros), pero, dependiendo de una situación dada, tiene que utilizar diferentes funciones al actualizar y crear. Ahora, probablemente podrías usar un if-else para lograr esto, sin embargo, ¿qué pasa si necesitas usar esta clase en un lugar diferente? En ese caso, tendrás que volver a escribir la misma instrucción if-else de nuevo. ¿No sería más fácil especificar tu contexto?

1
<?php
2
class User {
3
	
4
	public function CreateOrUpdate($name, $address, $mobile, $userid = null)
5
	{
6
		if( is_null($userid) ) {
7
			// it means the user doesn't exist yet, create a new record

8
		} else {
9
			// it means the user already exists, just update based on the given userid

10
		}
11
	}
12
}

Ahora, el patrón de estrategia "habitual" implica encapsular tus algoritmos dentro de otra clase, pero en este caso, otra clase sería un desperdicio. Recuerda que no tienes que seguir la plantilla exactamente. Las variaciones funcionan siempre que el concepto siga siendo el mismo y resuelva el problema.


Patrón de adaptador

El patrón de adaptador es un patrón de diseño estructural que te permite reutilizar una clase con una interfaz diferente, lo que le permite ser utilizado por un sistema que usa diferentes métodos de llamada.

Esto también te permite alterar algunas de las entradas que se reciben de la clase cliente, convirtiéndolas en algo compatible con las funciones del adaptado.

¿Cómo puedo usarlo?

Otro término para hacer referencia a una clase de adaptador es un contenedor, que básicamente te permite "ajustar" acciones en una clase y reutilizar estas acciones en las situaciones correctas. Un ejemplo clásico podría ser cuando estás creando una clase de dominio para las clases de tabla. En lugar de llamar a las diferentes clases de tabla y llamar a sus funciones una por una, podrías encapsular todos estos métodos en un método usando una clase adaptadora. Esto no solo te permitirá reutilizar cualquier acción que quieras, sino que también evitará que tengas que volver a escribir el código si necesitas usar la misma acción en un lugar diferente.

Compara estas dos implementaciones.

Enfoque sin adaptador

1
<?php
2
$user = new User();
3
$user->CreateOrUpdate( //inputs );

4
5
$profile = new Profile();
6
$profile->CreateOrUpdate( //inputs );

Si tuviéramos que hacer esto nuevamente en un lugar diferente, o incluso reutilizar este código en un proyecto diferente, tendríamos que escribir todo de nuevo.

Mejor

Eso se opone a hacer algo como esto:

1
<?php
2
$account_domain = new Account();
3
$account_domain->NewAccount( //inputs );

En esta situación, tenemos una clase contenedora, que sería nuestra clase de dominio Cuenta:

1
<?php
2
class Account()
3
{
4
	public function NewAccount( //inputs )

5
	{
6
		$user = new User();
7
		$user->CreateOrUpdate( //subset of inputs );

8
		
9
		$profile = new Profile();
10
		$profile->CreateOrUpdate( //subset of inputs );

11
	}
12
}

De esta manera, puedes volver a usar tu dominio Cuenta siempre que lo necesites, además, también podrás envolver otras clases en tu clase de dominio.


Patrón de método de fábrica

El patrón del método de fábrica es un patrón de diseño de creación que hace exactamente lo que creo: es una clase que actúa como una fábrica de instancias de objetos.

El objetivo principal de este patrón es encapsular el procedimiento de creación que puede abarcar diferentes clases en una sola función. Al proporcionar el contexto correcto al método de fábrica, podrás devolver el objeto correcto.

¿Cuándo puedo usarlo?

El mejor momento para utilizar el patrón del método de fábrica es cuando tiene varias variaciones diferentes 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 debas crear diferentes buttons; ¡Aquí es donde puedes usar una fábrica para crear los buttons por ti!

Comencemos creando nuestras tres clases:

1
<?php
2
abstract class Button {
3
	protected $_html;
4
	
5
	public function getHtml()
6
	{
7
		return $this->_html;
8
	}
9
}
10
11
class ImageButton extends Button {
12
	protected $_html = "..."; //This should be whatever HTML you want for your image-based button

13
}
14
15
class InputButton extends Button {
16
	protected $_html = "..."; //This should be whatever HTML you want for your normal button (<input type="button"... />);

17
}
18
19
class FlashButton extends Button {
20
	protected $_html = "..."; //This should be whatever HTML you want for your flash-based button

21
}

Ahora, podemos crear nuestra clase de fábrica:

1
<?php
2
class ButtonFactory
3
{
4
    public static function createButton($type)
5
    {
6
        $baseClass = 'Button';
7
        $targetClass = ucfirst($type).$baseClass;
8
 
9
        if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass)) {
10
            return new $targetClass;
11
		} else {
12
            throw new Exception("The button type '$type' is not recognized.");
13
		}
14
    }
15
}

Podemos usar este código de la siguiente forma:

1
$buttons = array('image','input','flash');
2
foreach($buttons as $b) {
3
    echo ButtonFactory::createButton($b)->getHtml()
4
}

La salida debe ser el HTML de todos los tipos button. De esta manera, podrás especificar qué botón crear según la situación y reutilizar la condición también.


Patrón de decorador

El patrón de decorador es un patrón de diseño estructural que nos permite agregar un comportamiento nuevo o adicional a un objeto durante el tiempo de ejecución, según la situación.

El objetivo es lograr que las funciones extendidas se puedan aplicar a una instancia específica y, al mismo tiempo, aún poder crear una instancia original que no tenga las nuevas funciones. También permite combinar varios decoradores para una instancia, de modo que no te quedes atascado con un decorador por cada instancia. Este patrón es una alternativa a la subclase, que hace referencia a la creación de una clase que hereda la funcionalidad de una clase principal. A diferencia de la subclase, que agrega el comportamiento en tiempo de compilación, "decorar" te permite agregar un nuevo comportamiento durante el tiempo de ejecución, si la situación lo requiere.

Para implementar el patrón de decorador, podemos seguir estos pasos:

  1. Subclasifica la clase "Component" original en una clase "Decorator"
  2. En la clase Decorator, agrega un indicador Component como campo
  3. Pasa un Component al constructor Decorator para inicializar el indicador Component
  4. En la clase Decorator, redirige todos los métodos "Component" al indicador "Component" y
  5. En la clase Decorator, anula cualquier método Component cuyo comportamiento deba modificarse

Pasos cortesía de http://en.wikipedia.org/wiki/Decorator_pattern

¿Cuándo puedo usarlo?

El mejor lugar para usar el patrón de decorador es cuando tienes una entidad que necesita tener un nuevo comportamiento solo si la situación lo requiere. Supongamos que tienes un elemento de enlace HTML, un enlace de cierre de sesión, al que quieres hacerle cosas ligeramente diferentes en función de la página actual. Para eso, podemos usar el patrón de decorador.

Primero, establezcamos las diferentes "decoraciones" que necesitaremos.

  • Si estamos en la página de inicio e iniciamos sesión, pide que este enlace se envuelva en etiquetas h2
  • Si estamos en una página diferente e iniciamos sesión, pide que este enlace se envuelva en etiquetas de subrayado
  • Si iniciamos sesión, ten este enlace envuelto en etiquetas fuertes

Una vez que hayamos establecido nuestras decoraciones, podemos empezar a programarlas.

1
<?php
2
class HtmlLinks {
3
	//some methods which is available to all html links

4
}
5
6
class LogoutLink extends HtmlLinks {
7
	protected $_html;
8
	
9
	public function __construct() {
10
		$this->_html = "<a href=\"logout.php\">Logout</a>";
11
	}
12
	
13
	public function setHtml($html)
14
	{
15
		$this->_html = $html;
16
	}
17
	
18
	public function render()
19
	{
20
		echo $this->_html;
21
	}
22
}
23
24
class LogoutLinkH2Decorator extends HtmlLinks {
25
	protected $_logout_link;
26
	
27
	public function __construct( $logout_link )
28
	{
29
		$this->_logout_link = $logout_link;
30
		$this->setHtml("<h2>" . $this->_html . "</h2>");
31
	}
32
	
33
	public function __call( $name, $args )
34
	{
35
		$this->_logout_link->$name($args[0]);
36
	}
37
}
38
39
class LogoutLinkUnderlineDecorator extends HtmlLinks {
40
	protected $_logout_link;
41
	
42
	public function __construct( $logout_link )
43
	{
44
		$this->_logout_link = $logout_link;
45
		$this->setHtml("<u>" . $this->_html . "</u>");
46
	}
47
	
48
	public function __call( $name, $args )
49
	{
50
		$this->_logout_link->$name($args[0]);
51
	}
52
}
53
54
class LogoutLinkStrongDecorator extends HtmlLinks {
55
	protected $_logout_link;
56
	
57
	public function __construct( $logout_link )
58
	{
59
		$this->_logout_link = $logout_link;
60
		$this->setHtml("<strong>" . $this->_html . "</strong>");
61
	}
62
	
63
	public function __call( $name, $args )
64
	{
65
		$this->_logout_link->$name($args[0]);
66
	}
67
}

Entonces deberíamos poder usarlo así:

1
$logout_link = new LogoutLink();
2
3
if( $is_logged_in ) {
4
	$logout_link = new LogoutLinkStrongDecorator($logout_link);
5
}
6
7
if( $in_home_page ) {
8
	$logout_link = new LogoutLinkH2Decorator($logout_link);
9
} else {
10
	$logout_link = new LogoutLinkUnderlineDecorator($logout_link);
11
}
12
$logout_link->render();

Podemos ver aquí cómo somos capaces de combinar múltiples decoradores si los necesitamos. Dado que todos los decoradores utilizan la función mágica __call, aún podemos llamar a los métodos de la función original. Si suponemos que estamos actualmente dentro de la página de inicio e iniciamos sesión, la salida HTML debe ser:

1
<strong><h2><a href="logout.php">Logout</a></h2></strong>

Patrón Singleton

El patrón de diseño singleton es un patrón de diseño de creación que garantiza que tengas una sola instancia de una clase determinada durante tu tiempo de ejecución y proporciona un punto de acceso global a la instancia única.

Esto hace que sea más fácil configurar un punto de "coordinación" para otros objetos que también usan la instancia de singleton, ya que las variables de singleton siempre serán las mismas para cualquier cosa que lo llame.

¿Cuándo puedo usarlo?

Si necesitas pasar una instancia específica de una clase a otra, puedes usar el patrón singleton para evitar tener que pasar la instancia a través del constructor o argumento. Imagina que creaste una clase Session, que simula la matriz global $_SESSION. Dado que esta clase solo necesitará ser instanciada una vez, podemos implementar un patrón singleton como este:

1
<?php
2
class Session
3
{
4
	private static $instance;
5
	
6
	public static function getInstance()
7
	{
8
		if( is_null(self::$instance) ) {
9
			self::$instance = new self();
10
		}
11
		return self::$instance;
12
	}
13
	
14
	private function __construct() { }
15
	
16
	private function __clone() { }
17
	
18
	//  any other session methods we might use
19
	...
20
	...
21
	...
22
}
23
24
// get a session instance
25
$session = Session::getInstance();

Al hacer esto, podemos acceder a nuestra instancia de sesión desde diferentes partes de nuestro código, incluso en diferentes clases. Estos datos persistirán en todas las llamadas getInstance.


Conclusión

Hay muchos más patrones de diseño por estudiar; en este artículo, solo destaqué algunos de los más importantes que uso al programar. Si estás interesado en leer sobre los otros patrones de diseño, la página Patrones de diseño de Wikipedia tiene mucha información. Si no es suficiente, siempre puedes consultar Patrones de diseño: elementos de software orientado a objetos reutilizables, que se considera uno de los mejores libros de patrones de diseño disponibles.

Una última cosa: cuando uses estos patrones de diseño, asegúrate siempre de que estás tratando de resolver el problema correcto. Como mencioné anteriormente, estos patrones de diseño son una espada de doble filo: si se usan en el contexto incorrecto, pueden empeorar las cosas; pero si se usan correctamente, se vuelven indispensables.

Si este tutorial te resultó útil, ¿por qué no revisas la gama de scripts PHP en Envato Market? Hay miles de scripts útiles que pueden acelerar tu desarrollo y ayudarte a lograr mejores resultados finales. Puedes encontrar sistemas de reserva, formularios de contacto AJAX, sistemas de boletines informativos y mucho más.

PHP scripts on Envato MarketPHP scripts on Envato MarketPHP scripts on Envato Market
Scripts PHP en Envato Market