André Alves de Lima

Talking about Software Development and more…

Um guia sobre Design Patterns para iniciantes (tradução)

Um guia sobre Design Patterns para iniciantes (tradução)

Fala pessoal, tudo tranquilo?
 
Estava eu dando uma olhada no blog do Renato Haddad quando encontrei um post dele falando deste artigo sobre Design Patterns, que achei muito, muito interessante.
 
Resolvi pedir permissão para traduzir o artigo em Português ao autor e ele autorizou… :) Muito obrigado Nikko Bautista!
 
O artigo está exatamente como o autor escreveu, só que traduzido para Português… Então, aí vai…
 
Hey guys, what’s up?
 
I was taking a look in Renato Haddad’s blog when I found a post talking about this article which explains some cool stuff about Design Patterns. I found it really, really interesting.
 
So, I decided to ask the author for a permission to translate this article to Portuguese and he authorised… :) Thank you very much indeed Nikko Bautista!
 
The article is exactly as the author wrote, but, translated to Portuguese… So, here you go…


Você já se perguntou o quê são Design Patterns? Neste artigo, vou explicar por quê Design Patterns são importantes, e vou disponibilizar alguns exemplos, em PHP, de quando e porquê eles devem ser utilizados.
 
O quê são Design Patterns?
 
Design Patterns são soluções reutilizáveis e otimizadas para problemas de programação que encontramos todos os dias. Um Design Pattern não é uma classe ou biblioteca que simplesmente plugamos no nosso sistema; é muito mais do que isso. É um template que deve ser implementado da maneira correta. Também não é específico de uma linguagem. Um bom Design Pattern deve ser implementável na maioria – se não todas – as linguagens, dependendo de suas capacidades. O mais importante: um Design Pattern pode ser uma faca de dois gumes – se implementado no lugar errado, pode ser desastroso e pode criar muitos problemas para você. Entretanto, se implementado no lugar correto, aonde realmente é necessário, pode ser a sua salvação.
 
Existem três tipos básicos de Design Patterns:
 
    • Estruturais
    • Criacionais
    • Comportamentais
Patterns estruturais geralmente tratam de relacionamentos entre entidades, fazendo com que essas entidades juntas funcionem de uma forma melhor.
 
Patterns criacionais fornecem mecanismos de instanciação, tornando mais fácil a criação de objetos de forma que eles se adequem a cada situação.
 
Patterns comportamentais são utilizados na comunicação entre entidades e fazem com que essas entidades se comuniquem mais facilmente e flexivelmente.
 
Por quê eu devo utilizá-los?
 
Design Patterns são, por princípio, soluções bem pensadas para problemas de programação. Muitos programadores encontraram esses problemas antes, e usaram essas "soluções" para resolvê-los. Se você encontrar esses problemas, por quê recriar a solução quando você pode utilizar uma que já está pronta?
 
Exemplo
 
Vamos imaginar que pediram para você, de alguma forma, juntar (merge) duas classes que fazem duas coisas distintas. Essas duas classes são amplamente utilizadas no sistema em diferentes lugares, o que torna difícil remover essas duas classes e alterar o código atual. Além disso, alterar o código atual faz com que você tenha que testar qualquer código alterado, já que esse tipo de alteração, em um sistema que depende de vários componentes, quase sempre introduz novos bugs. Ao invés de fazer isso, você pode implementar uma variação dos patterns strategy e adapter, que lidam facilmente com esses tipos de cenários.
 
<?php
class StrategyAndAdapterExampleClass {
	private $_class_one;
	private $_class_two;
	private $_context;

	public function __construct( $context ) {
			$this->_context = $context;
	}

	public function operation1() {
		if( $this->_context == "context_for_class_one" ) {
			$this->_class_one->operation1_in_class_one_context();
		} else ( $this->_context == "context_for_class_two" ) {
			$this->_class_two->operation1_in_class_two_context();
		}
	}
}
 
Bem simples, certo? Agora vamos dar uma olhada mais de perto no pattern strategy.
 
Strategy Pattern
 
 
 
"O pattern strategy é um Design Pattern comportamental que permite que você decida qual fluxo de ação um programa deve tomar, baseado num contexto específico fornecido em tempo de execução. Você encapsula dois algoritmos diferentes dentro de duas classes, e decide em tempo de execução qual estratégia deve ser seguida."
 
No nosso exemplo acima, a estratégia é baseada no tipo que a variável $context era na hora em que ela foi instanciada. Se você fornece o contexto para class_one, vai utilizar class_one, e vice-versa.
 
Lindo, mas, quando eu uso isso?
 
 
Imagine que você está desenvolvendo uma classe que pode tanto atualizar quanto criar um novo registro de usuário. Ambos precisam das mesmas entradas (nome, endereço, celular, etc.), mas, dependendo da situação, devemos utilizar funções diferentes quando vamos atualizar ou criar. Agora, você provavelmente poderia simplesmente utilizar um if-else para fazer isso, mas, e se você precisar utilizar essa classe em um lugar diferente? Nesse caso, você vai ter que re-escrever o mesmo if-else novamente. Não seria mais fácil simplesmente especificar o contexto?
 
<?php
class User {

	public function CreateOrUpdate($name, $address, $mobile, $userid = null)
	{
		if( is_null($userid) ) {
			// it means the user doesn't exist yet, create a new record
		} else {
			// it means the user already exists, just update based on the given userid
		}
	}
}
 
O pattern strategy "usual" envolve encapsular os seus algoritmos dentro de outra classe, mas, nesse caso, escrever outra classe seria perda de tempo. Lembre que você não tem que seguir o template exatamente como ele é. Variações podem ser feitas, desde que o conceito permaneça o mesmo e que o código final resolva o problema.
 
Adapter Pattern
 
 
"O adapter pattern é um Design Pattern estrutural que permite que você altere o propósito de uma classe com uma interface distinta, permitindo que essa classe seja utilizada por um sistema que tenha métodos de chamada diferentes."
Isso também faz com que você altere algumas das entradas que são recebidas da classe cliente, fazendo com que ela se torne algo compatível com as funções do "adaptado".
 
Como eu posso utilizar isso?
 
 
Um outro termo utilizado para referenciarmos a uma classe adaptador é wrapper, que basicamente permite que você "envolva" (wrap) ações dentro de uma classe e reutilize essas ações nas situações necessárias. Um exemplo clássico é quando você está criando classes de domínio para classes de suas tabelas. Ao invés de chamar diferentes classes para cada tabela e chamar as funções delas uma a uma, você pode encapsular todos esses métodos em um só e utilizar uma classe "adaptor". Isso não só permite que você reutilize qualquer ação que você queira, mas também evita que você tenha que re-escrever o código se precisar utilizar a mesma ação em locais diferentes.
 
Compare essas duas implementações.
 
Alternativa não-adapter
 
<?php
$user = new User();
$user->CreateOrUpdate( //inputs );

$profile = new Profile();
$profile->CreateOrUpdate( //inputs );
 
Se nós tivéssemos que fazer isso novamente em um lugar diferente, ou se tivéssemos que re-utilizar esse código em um projeto diferente, teríamos que escrever tudo de novo.
 
Melhor
 
É o oposto de fazer algo como isto:
 
<?php
$account_domain = new Account();
$account_domain->NewAccount( //inputs );
 
Nessa situação, nós temos uma classe "wrapper", que poderia ser a nossa classe de domínio de Accounts:
 
<?php
class Account()
{
	public function NewAccount( //inputs )
	{
		$user = new User();
		$user->CreateOrUpdate( //subset of inputs );

		$profile = new Profile();
		$profile->CreateOrUpdate( //subset of inputs );
	}
}
 
Dessa forma, você pode utilizar a sua classe de domínio de Accounts novamente aonde quer que você precisasse – e mais, você também poderia envolver outras classes abaixo da sua classe de domínio.
 
Factory Method Pattern
 
 
 
"O factory method pattern é um Design Pattern criacional que faz exatamente o que o próprio nome já diz: é uma classe que age como uma fábrica de instâncias de objetos."
 
A principal função desse pattern é encapsular o procedimento criacional que pode gerar diferentes classes em uma única função. Ao disponibilizar o contexto correto ao método factory, ele vai retornar uma instância do objeto que você está querendo.
 
Onde eu posso utilizar isso?
 
 
O melhor lugar para utilizarmos o método factory é nos locais em que temos diferentes variações de uma mesma entidade. Digamos que você tenha uma classe "botão"; essa classe tem diferentes variações, somo "ImageButton", "InputButton" e "FlashButton". Dependendo do local, você pode querer criar botões diferentes – esse é o local onde você pode utilizar a factory para criar os botões pra você!
 
Vamos começar criando nossas três classes:
 
<?php
abstract class Button {
	protected $_html;

	public function getHtml()
	{
		return $this->_html;
	}
}

class ImageButton extends Button {
	protected $_html = "..."; //This should be whatever HTML you want for your image-based button
}

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

class FlashButton extends Button {
	protected $_html = "..."; //This should be whatever HTML you want for your flash-based button
}
 
Agora nós criamos a nossa classe factory:
 
<?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("The button type '$type' is not recognized.");
		}
    }
}
 
Então podemos utilizar esse código desta forma:
 
$buttons = array('image','input','flash');
foreach($buttons as $b) {
    echo ButtonFactory::createButton($b)->getHtml()
}
 
A saída deve ser o HTML de todos os tipos de botões. Dessa forma, você é capaz de especificar qual botão criar dependendo da situação, e bem como reutilizar essa condição.
 
Decorator Pattern
 
 
 
"O decorator pattern é um Design Pattern estrutural que permite incluirmos um comportamento novo ou adicional a um objeto em tempo de execução, dependendo da situação."
 
O objetivo é que as funções extendidas possam ser aplicadas a uma instância específica, e, ao mesmo tempo, ainda podermos criar uma instância original, que não tem essas funções novas. Esse pattern também permite que combinemos vários decorators para uma mesma instância, a fim de que não fiquemos presos a um decorator para cada instância. O decorator é uma alternativa a "subclassing", que se refere a criar classes que herdam funcionalidades de uma classe pai. Ao contrário de "subclassing", que adiciona o comportamento em tempo de compilação, a prática de "decorating" permite que você adicione novos comportamentos em tempo de execução, se necessário.
 
Para implementar um decorator pattern, podemos seguir esses passos:
 
1. Fazer uma subclasse do componente original em uma classe decorator
2. Na classe decorator, adicionar um ponteiro para o componente como um atributo dessa classe
3. Passar o componente no construtor do decorator para inicializar esse atributo
4. Na classe decorator, redirecionar todos os métodos do componente para o ponteiro do componente
5. Na classe decorator, sobrescrever todos os métodos em que o comportamento precise ser modificado
 
 
Quando posso utilizar isso?
 
 
O melhor lugar para utilizar o pattern decorator é quando você tem uma entidade que precisa ter novos comportamentos somente em algumas situações. Vamos supor que você tenha um link para logout, que você quer que tenha um comportamento diferente dependendo da página corrente. Para isso, podemos utilizar o pattern decorator.
 
Primeiro, vamos estabelecer as "decorations" diferentes que teremos.
 
  • Se estivermos na home page e estivermos logados, esse link deverá estar entre tags h2
  • Se estivermos em uma outra página qualquer e estivermos logados, esse link deverá estar entre tags underline
  • Se não estivermos logados, esse link deverá estar entre tags strong
Uma vez que estabelecemos nossas "decorations", podemos programá-las.
 
<?php
class HtmlLinks {
	//some methods which is available to all html links
}

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]);
	}
}
 
Então podemos utilizar esse código desta forma:
 
$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();
 
Podemos verificar acima como é possível combinar múltiplas "decorations" caso isso seja necessário. Uma vez que todas as "decorations" usam a função __call, podemos ainda utilizar as chamadas originais. Se assumirmos que estamos na home page e estamos logados, o código HTML deve ser:
 
<strong><h2><a href="logout.php">Logout</a></h2></strong>
 
Singleton Pattern
 
 
"O singleton pattern é um Design Pattern que assegura que tenhamos uma única instância de uma classe em particular durante o tempo de execução, e provê um ponteiro global para acessar essa instância única."
 
Isso faz com que tenhamos um único ponto de "coordenação" que será utilizado por todos os outros objetos que chamam esse singleton.
 
Onde posso utilizar isso?
 
 
 
Se você precisa passar uma instância específica de uma classe para outra, você pode utilizar o pattern singleton para evitar que você tenha que passar a instância pelo construtor ou através de uma propriedade. Imagine que você criou uma classe Session, que simula o array global $_SESSION. Como essa classe terá que ser instanciada somente uma única vez, podemos implementar o pattern singleton da seguinte forma:
 
<?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() { }

	//  any other session methods we might use
	...
	...
	...
}

// get a session instance
$session = Session::getInstance();
 
Ao fazer isso, podemos acessar a instância de Session em diferentes partes do nosso código, até mesmo em diferentes classes. A instância retornada será a mesma, independente de quem chamar getInstance.
 
Conclusão
 
Existem muitos outros Design Patterns para estudar; nesse artigo somente destaquei os que mais utilizo quando programo no dia-a-dia. Se você está interessado em ler sobre outros Design Patterns, a página de Design Patterns da Wikipedia tem muita informação. Se não for suficiente, você sempre pode dar uma olhada no Design Patterns: Elements of Reusable Object-Oriented Software, que é considerado um dos melhores livros sobre Design Patterns disponíveis no mercado.
 
Uma última coisa: quando você utilizar esses Design Patterns, sempre assegure-se de que você está tentando resolver o problema certo. Como mencionei anteriormente, Design Patterns são facas de dois gumes: se utilizados no contexto errado, podem potencialmente fazer com que as coisas piorem; mas, se utilizados da maneira correta, se tornam indispensáveis.

Nikko Bautista.
Translated by André Alves de Lima.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *