Os padrões de design são apenas para arquitetos Java - pelo menos é o que você sempre pensou. Na verdade, os padrões de design são úteis para todos. Se essas ferramentas não são propriedade dos “astronautas arquitetônicos”, então o que são? Por que eles são úteis em aplicativos PHP? Este artigo explica esses problemas.
O livro Design Patterns introduziu padrões de design à comunidade de software. Os autores do livro são Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides Design (comumente conhecido como "Gangue dos Quatro"). Os conceitos básicos por trás dos padrões de design apresentados são muito simples. Depois de anos praticando desenvolvimento de software, Gamma e outros descobriram certos padrões com projetos fixos, assim como os arquitetos projetam casas e edifícios, desenvolvendo modelos para onde um banheiro deveria ser ou como uma cozinha deveria ser construída. Usar esses modelos, ou padrões de projeto, significa projetar edifícios melhores com mais rapidez. O mesmo conceito se aplica ao software.
Os padrões de projeto não apenas representam uma maneira útil de desenvolver software robusto com mais rapidez, mas também fornecem uma maneira de encapsular grandes ideias em termos amigáveis. Por exemplo, você poderia dizer que está escrevendo um sistema de mensagens que fornece acoplamento fraco ou que está escrevendo um padrão chamado Observer.
Demonstrar o valor dos padrões com exemplos menores é muito difícil. Muitas vezes isso parece um exagero, já que os padrões realmente funcionam em grandes bases de código. Este artigo não demonstra um aplicativo grande, portanto, você precisa pensar em maneiras de aplicar os princípios do exemplo em seu próprio aplicativo grande — e não no código demonstrado neste artigo. Isso não quer dizer que você não deva usar padrões em aplicações pequenas. Muitos aplicativos bons começam como aplicativos pequenos e progridem para aplicativos grandes, portanto, não há razão para não desenvolver esses tipos de práticas de codificação sólidas. Agora que você entende os padrões de design e por que eles são úteis, vamos dar uma olhada nos cinco padrões comumente usados no PHP V5.
Padrão de fábrica
Originalmente no livro Design Patterns, muitos padrões de design incentivam o uso de acoplamento fraco. Para entender esse conceito, é melhor falar sobre a árdua jornada que muitos desenvolvedores percorrem trabalhando em grandes sistemas. Quando você altera um trecho de código, podem ocorrer problemas e quebras em cascata podem ocorrer em outras partes do sistema – partes que antes você pensava que não tinham nenhuma relação.
O problema é o acoplamento apertado. Funções e classes em uma parte do sistema dependem fortemente do comportamento e da estrutura de funções e classes em outras partes do sistema. Você deseja um conjunto de padrões que permita que essas classes se comuniquem entre si, mas não deseja amarrá-las firmemente para evitar interligações. Em sistemas grandes, muito código depende de algumas classes principais. Podem surgir dificuldades quando essas classes precisam ser alteradas. Por exemplo, suponha que você tenha uma classe User que lê um arquivo. Você deseja alterá-lo para uma classe diferente que lê o banco de dados; no entanto, todo o seu código faz referência à classe original que lê o arquivo. Neste momento, será muito conveniente usar o modo de fábrica.
Padrão de fábrica é uma classe que possui certos métodos que criam objetos para você. Você pode usar classes de fábrica para criar objetos sem usar new diretamente. Desta forma, se quiser alterar o tipo de objeto criado, basta alterar a fábrica. Todo o código que usa esta fábrica é alterado automaticamente.
A Listagem 1 mostra um exemplo de classe de fábrica. O lado do servidor da equação consiste em duas partes: um banco de dados e um conjunto de páginas PHP que permitem adicionar comentários, solicitar uma lista de comentários e obter artigos relacionados a um feedback específico.
Listagem 1. Factory1.php
<?php interface IUser { função getNome(); }
classe Usuário implementa IUser { função pública __construct( $id ) { }
função pública getNome() { retorne "Jack"; } }
classe UserFactory { função estática pública Criar ($id) { retornar novo usuário($id); } }
$uo = UserFactory::Create( 1 ); echo($uo->getNome()."n" ); ?> |
A interface IUser define quais ações um objeto de usuário deve executar. A implementação de IUser é chamada de User, e a classe de fábrica UserFactory cria objetos IUser. Esse relacionamento pode ser representado pela UML na Figura 1.
Figura 1. Classe Factory e sua interface IUser e classe de usuário relacionadas |
Se você executar este código na linha de comando usando o interpretador php, obterá os seguintes resultados:
O código de teste solicitará o objeto User da fábrica e gerará o resultado do método getName.
Existe uma variante do padrão de fábrica que utiliza métodos de fábrica. Esses métodos estáticos públicos em uma classe constroem objetos desse tipo. Este método é útil se for importante criar objetos deste tipo. Por exemplo, suponha que você precise criar um objeto e depois definir diversas propriedades. Esta versão do padrão de fábrica encapsula o processo em um único local, para que você não precise copiar códigos de inicialização complexos e colá-los em toda a base de código. A Listagem 2 mostra um exemplo de utilização de um método de fábrica.
Listagem 2. Factory2.php
<?php interface IUser { função getNome(); }
classe Usuário implementa IUser { função estática pública Load( $id ) { retornar novo usuário($id); }
função estática pública Criar ( ) { retornar novo usuário(nulo); }
função pública __construct( $id ) { }
função pública getNome() { retorne "Jack"; } }
$uo = Usuário::Carregar( 1 ); echo($uo->getNome()."n" ); ?> |
Este código é muito mais simples. Possui apenas uma interface IUser e uma classe User que implementa esta interface. A classe User possui dois métodos estáticos para criação de objetos. Esse relacionamento pode ser representado pela UML na Figura 2.
Figura 2. Interface IUser e classe de usuário com método de fábrica |
A execução do script na linha de comando produz os mesmos resultados da Listagem 1, como segue:
Conforme mencionado acima, às vezes esses modos podem parecer um exagero em ambientes menores. No entanto, é melhor aprender esta forma sólida de codificação que pode ser aplicada a projetos de qualquer tamanho.
Modo de elemento único
Alguns recursos do aplicativo são exclusivos porque existe apenas um recurso desse tipo. Por exemplo, as conexões com um banco de dados por meio de um identificador de banco de dados são exclusivas. Você deseja compartilhar o identificador do banco de dados em seu aplicativo porque é uma sobrecarga ao manter a conexão aberta ou fechada, ainda mais durante o processo de busca de uma única página.
O modo de elemento único satisfaz este requisito. Se o aplicativo contiver um e apenas um objeto por vez, esse objeto será um singleton. O código na Listagem 3 mostra um elemento único de conexão com o banco de dados no PHP V5.
Listagem 3. Singleton.php
<?php require_once("DB.php");
classe DatabaseConnection { função estática pública get() { estático $db = null; if ($db == nulo) $db = new DatabaseConnection(); return $db; }
privado $_handle=nulo; função privada __construct() { $dsn = 'mysql://root:senha@localhost/fotos'; $this->_handle =& DB::Connect( $dsn, array() ); }
identificador de função pública() { return $this->_handle; } }
print( "Handle = ".DatabaseConnection::get()->handle()."n" ); print( "Handle = ".DatabaseConnection::get()->handle()."n" ); ?> |
Este código mostra uma única classe chamada DatabaseConnection. Você não pode criar seu próprio DatabaseConnection porque o construtor é privado. Mas usando o método get estático, você pode obter e obter apenas um objeto DatabaseConnection. A UML para este código é mostrada na Figura 3.
Figura 3. Elemento único de conexão com o banco de dados |
A melhor prova é que o identificador do banco de dados retornado pelo método handle é o mesmo entre as duas chamadas. Você pode executar o código na linha de comando para observar isso.
% php singleton.php Alça = ID do objeto nº 3 Alça = ID do objeto nº 3 % |
Os dois identificadores retornados são o mesmo objeto. Se você usar um elemento único de conexão com o banco de dados em todo o aplicativo, poderá reutilizar o mesmo identificador em qualquer lugar.
Você pode usar variáveis globais para armazenar identificadores de banco de dados; no entanto, essa abordagem só é adequada para aplicativos menores. Em aplicações maiores, evite usar variáveis globais e use objetos e métodos para acessar recursos. Padrão de observador
O padrão Observer oferece outra maneira de evitar o acoplamento forte entre os componentes. O padrão é muito simples: um objeto se torna observável adicionando um método que permite que outro objeto, o observador, registre-se. Quando um objeto observável muda, ele envia mensagens aos observadores registrados. Esses observadores usam essas informações para realizar operações independentes do objeto observável. O resultado é que os objetos podem conversar entre si sem precisar entender por quê. Um exemplo simples é uma lista de usuários no sistema. O código da Listagem 4 exibe uma lista de usuários e, quando um usuário é adicionado, envia uma mensagem. Esta lista pode ser observada através de um observador de log que envia mensagens quando um usuário é adicionado.
Listagem 4. Observer.php
<?php interface IObserver { função onChanged($remetente, $args); }
interface IObservável { função addObservador($observador); }
classe UserList implementa IObservable { private $_observers = array();
função pública addCustomer($nome) { foreach($this->_observers as $obs) $obs->onChanged( $this, $nome ); }
função pública addObserver($observador) { $this->_observers []= $observer; } }
classe UserListLogger implementa IObserver { função pública onChanged($sender, $args) { echo( "'$args' adicionado à lista de usuáriosn" ); } }
$ul = new ListaUtilizador(); $ul->addObserver( new UserListLogger() ); $ul->addCustomer("Jack"); ?> |
Este código define quatro elementos: duas interfaces e duas classes. A interface IObservable define objetos que podem ser observados, e UserList implementa essa interface para se registrar como observável. A lista IObserver define como se tornar um observador. UserListLogger implementa a interface IObserver. Esses elementos são mostrados na UML na Figura 4.
Figura 4. Lista de usuários observável e registrador de eventos de lista de usuários |
Se você executá-lo na linha de comando, verá a seguinte saída:
% php observador.php 'Jack' adicionado à lista de usuários % |
O código de teste cria uma UserList e adiciona o observador UserListLogger a ela. Em seguida, adicione um consumidor e notifique o UserListLogger sobre essa alteração.
É fundamental perceber que UserList não sabe o que o criador de logs fará. Pode haver um ou mais ouvintes que realizam outras operações. Por exemplo, você pode ter um observador que envia uma mensagem aos novos usuários, dando-lhes as boas-vindas ao sistema. O valor dessa abordagem é que UserList ignora todos os objetos que dependem dele e se concentra principalmente na manutenção da lista de usuários e no envio de mensagens quando a lista é alterada.
Este padrão não está limitado a objetos na memória. É a base para sistemas de consulta de mensagens baseados em banco de dados usados em aplicações maiores. modo de cadeia de comando
O padrão de cadeia de comando é baseado em tópicos fracamente acoplados, enviando mensagens, comandos, solicitações ou qualquer outra coisa por meio de um conjunto de manipuladores. Cada manipulador faz seu próprio julgamento sobre se pode lidar com a solicitação. Se puder, a solicitação será processada e o processo será interrompido. Você pode adicionar ou remover manipuladores do sistema sem afetar outros manipuladores. A Listagem 5 mostra um exemplo desse padrão.
Listagem 5. Chain.php
<?php interfaceICommand { função onCommand($nome, $args); }
classe CommandChain { private $_commands = array();
função pública addCommand($cmd) { $this->_commands []= $cmd; }
função pública runCommand($nome, $args) { foreach($this->_commands as $cmd) { if ($cmd->onCommand( $nome, $args ) ) retornar; } } }
classe UserCommand implementa ICommand { função pública onCommand($nome, $args) { if ($nome!= 'addUser') retorna falso; echo("UserCommand manipulando 'addUser'n" ); retornar verdadeiro; } }
classe MailCommand implementa ICommand { função pública onCommand($nome, $args) { if ($nome!= 'correio') retorna falso; echo("MailCommand manipulando 'mail'n" ); retornar verdadeiro; } }
$cc = new CommandChain(); $cc->addCommand( new UserCommand() ); $cc->addCommand( new MailCommand() ); $cc->runCommand('addUser', null); $cc->runCommand('correio', null); ?> |
Este código define a classe CommandChain que mantém uma lista de objetos ICommand. Ambas as classes podem implementar a interface ICommand - uma que responde a solicitações de email e a outra que responde à adição de usuários. A Figura 5 mostra a UML.
Figura 5. Cadeia de comandos e seus comandos relacionados |
Se você executar um script que contenha algum código de teste, obterá a seguinte saída:
% php cadeia.php UserCommand manipulando 'addUser' MailCommand manipulando 'mail' % |
O código primeiro cria o objeto CommandChain e adiciona duas instâncias do objeto de comando a ele. Em seguida, execute dois comandos para ver quem respondeu aos comandos. Se o nome do comando corresponder a UserCommand ou MailCommand, o código falhará e nenhuma ação ocorrerá. O padrão de cadeia de comando é valioso ao criar uma arquitetura escalonável para lidar com solicitações, e muitos problemas podem ser resolvidos usando-o. padrão de estratégia
O último padrão de design que abordamos é o Strategy Pattern. Neste padrão, os algoritmos são extraídos de classes complexas e podem, portanto, ser facilmente substituídos. Por exemplo, se você deseja alterar a forma como as páginas são classificadas nos motores de busca, o modo Estratégia é uma boa escolha. Pense nas partes de um mecanismo de pesquisa – um que percorre as páginas, um que classifica cada página e outro que classifica os resultados com base na classificação. Em exemplos complexos, essas peças estão todas na mesma classe. Usando o padrão Strategy, você pode colocar a parte do arranjo em outra classe para alterar a forma como a página é organizada sem afetar o restante do código do mecanismo de busca.
Como um exemplo mais simples, a Listagem 6 mostra uma classe de lista de usuários que fornece uma maneira de localizar um conjunto de usuários com base em um conjunto de políticas plug-and-play.
Listagem 6. Estratégia.php
<?php interface IStrategy { função filtro($registro); }
classe FindAfterStrategy implementa IStrategy { privado $_nome;
função pública __construct($nome) { $this->_nome = $nome; }
filtro de função pública ($record) { return strcmp($this->_name, $record) <= 0; } }
classe RandomStrategy implementa IStrategy { filtro de função pública ($record) { retornar rand(0, 1) >= 0,5; } }
classe UserList { privado $_lista=array();
função pública __construct($nomes) { if ($nomes!=nulo) { foreach($nomes como $nome) { $this->_list []= $nome; } } }
função pública adicionar($nome) { $this->_list []= $nome; }
função pública encontrar($filtro) { $recs = array(); foreach($this->_list as $usuário) { if ($filtro->filtro($usuário)) $recs []= $usuário; } return $recs; } }
$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) ); $f1 = $ul->find(new FindAfterStrategy( "J" ) ); imprimir_r($f1);
$f2 = $ul->find(new RandomStrategy() ); imprimir_r($f2); ?> |
Figura 6. Lista de usuários e política usada para selecionar usuários |
A classe UserList é um wrapper em torno de uma matriz de nomes. Ele implementa o método find, que utiliza uma das diversas estratégias para selecionar um subconjunto desses nomes. Essas estratégias são definidas pela interface IStrategy, que possui duas implementações: uma que seleciona aleatoriamente o usuário e outra que seleciona todos os nomes após o nome especificado. Ao executar o código de teste, você obtém a seguinte saída:
%php estratégia.php Variedade ( [0] => Jack [1] =>Lori [2] =>Megan ) Variedade ( [0] => Andy [1] =>Megan ) % |
O código de teste executa a mesma lista de usuários para ambas as estratégias e exibe os resultados. No primeiro caso, a estratégia procura qualquer nome que siga J, então você obteria Jack, Lori e Megan. A segunda estratégia escolhe nomes aleatoriamente, produzindo resultados diferentes a cada vez. Nesse caso, os resultados são Andy e Megan.
O padrão Strategy é ideal para sistemas complexos de gerenciamento de dados ou sistemas de processamento de dados que exigem um alto grau de flexibilidade na forma como os dados são filtrados, pesquisados ou processados.
Conclusão
Este artigo apresenta apenas alguns dos padrões de design mais comuns usados em aplicativos PHP. Mais padrões de design são demonstrados no livro Design Patterns. Não deixe que a mística da arquitetura o desencoraje. Os padrões são uma ideia maravilhosa que funciona em qualquer linguagem de programação e em qualquer nível de habilidade.