Olá
Hoje veremos o padrão Composite, que é um padrão que tem um nome meio que “auto-explicativo”; Composite é um padrão que define objetos que é formado pela composição de outros objetos, permitindo assim tratar vários objetos como se fosse um só.
Para exemplificar o funcionamento do padrão composite, vamos utilizar a classe guerreiro utilizada na parte 3 do artigo; Caso você perdeu, leia aqui; Veja novamente a nossa implementação de Guerreiro:
//Implementação de Guerreiro class Guerreiro { private $arma; public function __construct(Arma $arma) { $this->arma = $arma; } // Outros métodos do Guerreiro ... public function lutar() { // Implementação da luta $this->arma->golpear(); } }
Para iniciar uma “Guerra”, basta criar alguns guerreiros, e mandar eles lutar! Portanto:
$guerreiro1 = new Guerreiro(new ArcoFlecha()); $guerreiro2 = new Guerreiro(new Espada()); $guerreiro3 = new Guerreiro(new Machado()); $guerreiro1->lutar(); $guerreiro2->lutar(); $guerreiro3->lutar();
Veja que o trabalho de manutenção nesse código é relativamente grande, pois quando for necessário adicionar mais guerreiros, será preciso sempre adicionar mais duas linhas de código: Uma para instanciar um novo objeto, e outra para mandar o guerreiro lutar, e é nesse cenário que o padrão Composite vem nos ajudar.
O padrão composite permite a manipulação de vários objetos como se fossem um objeto só, facilitando assim a nossa “guerra”, ou seja, podemos criar um “exército de guerreiros” e mandar todo mundo lutar com apenas um comando. Sendo assim, primeiramente temos que extrair uma interface que represente os nossos guerreiros, para que seja possivel alternar entre o uso da nossa classe Guerreiro definida acima, e o uso do nosso composite que compõe vários guerreiros. A nivel de exemplo, vamos manter apenas o método “lutar”, e a interface vai ficar da seguinte forma:
interface IGuerreiro { public function lutar(); }
Para quem não sabe, uma interface é como se fosse um contrato, onde as classes que a implementam devem ter os métodos e parâmetros definidos por ela. Neste caso, as classes que implementam IGuerreiro devem implementar apenas o método lutar.
//Implementação anterior de Guerreiro, agora implementando IGuerreiro class Guerreiro implements IGuerreiro { private $arma; public function __construct(Arma $arma) { $this->arma = $arma; } public function lutar() { // Implementação da luta $this->arma->golpear(); } } // Implementação do composite de Guerreiro class GuerreiroComposite implements IGuerreiro { private $guerreiros; // Método que adiciona os guerreiros no composite public function add(IGuerreiro $guerreiro) { $this->guerreiros[] = $guerreiro; } // O método lutar deve fazer todos os guerreiros lutarem public function lutar() { foreach ($guerreiro => $this->guerreiros) { $guerreiro->lutar(); } } }
Agora para enviar um exercito de guerreiros para a batalha, basta criar alguns guerreiros, e adicioná-los no composite através do método add, e pronto! Notem que o método add contem o tipo permitido para o parâmetro, para impedir que qualquer outra classe seja adicionada no Composite, mesmo essa outra classe contendo os mesmos métodos da interface. No exemplo a seguir, temos um exercito de arqueiros partindo pra luta:
$exercitoDeArqueiros = new GuerreiroComposite(); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); $exercitoDeArqueiros->lutar();
Como tanto Guerreiro quanto GuerreiroComposite implementam a interface IGuerreiro, podemos fazer um Composite conter outro composite, como no exemplo a seguir:
$exercitoDeArqueiros = new GuerreiroComposite(); $exercitoDeArqueiros->add(new Guerreiro(new ArcoFlecha())); // .. Demais arqueiros $exercitoDaMachadinha = new GuerreiroComposite(); $exercitoDaMachadinha->add(new Guerreiro(new Machado())); // .. Demais guerreiros de machado $exercitoNervoso = new GuerreiroComposite(); $exercitoNervoso->add(new Guerreiro(new Espada()); // Tem um diferentão que resolveu lutar de espada $exercitoNervoso->add($exercitoDaMachadinha); $exercitoNervoso->add($exercitoDeArqueiros); $exercitoNervoso->lutar();
Agora que ja foi explicado o funcionamento do padrão, vamos passá-lo para o nosso Framework.
Toda boa aplicação Web possui alguns elementos que são fundamentais para que ela funcione. Hoje em dia, por exemplo, não encontramos muitas aplicações que não trabalham com banco de dados, ou que não armazenem informações de acesso, e com as aplicações que possivelmente serão desenvolvidas com o nosso framework não é diferente, e sendo assim, vamos supor que nosso sistema irá trabalhar com o banco Mysql, e para isso vamos adicionar uma conexão simples de banco de dados mysql ao nosso projeto:
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $config->configure(); // Vamos adicionar aqui para não precisar adicionar dentro dos commands $dbname="teste"; // Indique o nome do banco de dados que será aberto $usuario="admin"; // Indique o nome do usuário que tem acesso $password="SenhaDoAdmin"; // Indique a senha do usuário mysql_connect("localhost",$usuario,$password) or dir(mysql_error()); mysql_select_db($dbname,$id) or die(mysql_error()); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); mysql_close(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Observação: Não está em discussão nesse artigo qual a melhor forma ou ferramente de conexão com banco, a idéia aqui é exemplificar a localização desse tipo de mecanismo
![]()
Apesar de estar no ponto correto da execução, ou seja, conectando antes da execução do command e fechando após, o local da conexão não está apropriado, pois estamos misturando configuração/codigo de banco de dados com a execução do nosso framework, que é o papel do index, e tambem temos o problema de necessitar alterar o index a cada cliente que desejarmos configurar.
Alguns de vocês devem estar pensando: “Porque não cria um arquivo separado com a conexão e faz um include?”. Essa realmente é uma prática utilizada por vários programadores PHP, e que com certeza resolve nosso problema, porem devemos remover o mysql_close do final do arquivo, se não iremos condicionar o sistema a utilizar somente mysql (o que não é desejável); Vejamos como ficou:
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $config->configure(); // Incluimos o arquivo de conexão com todo o código da conexão include("conexao.php"); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); // mysql_close(); Removido!! } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Com certeza funciona, mas podemos utilizar uma solução mais orientada a objetos, não podemos? Então vamos criar uma classe para essa conexão. Ela conterá dois métodos: init e finish, que serão executados antes e depois da execução respectivamente;
Conexao.php
class Conexao { public function start() { $dbname="teste"; // Indique o nome do banco de dados que será aberto $usuario="admin"; // Indique o nome do usuário que tem acesso $password="SenhaDoAdmin"; // Indique a senha do usuário mysql_connect("localhost",$usuario,$password) or dir(mysql_error()); mysql_select_db($dbname,$id) or die(mysql_error()); } public function finish() { mysql_close(); // Agora da pra colocar novamente } }
Adicionando a utilização da classe e removendo a inclusão do arquivo no index:
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $conn = new Conexao(); // Sera carregado via __autoload $config->configure(); $conn->start(); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); $conn->finish(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Agora sim, temos uma solução totalmente orientada a objetos (totalmente não, pois a conexão com o mysql não é OO), e podemos utilizar o Mysql em nossa aplicação, mas ainda não temos uma solução ideal, pois o mecanismo está preso a classe Conexao, e caso existir a necessidade de alternar entre tipos diferentes de conexão ou simplesmente alterar a configuração, por exemplo, ficamos presos a edição do arquivo conexao.php;
Vamos então alterar nosso código para utilizar o padrão Strategy, que foi visto na parte 3, que irá permitir a gente trocar a classe de implementação da conexão, permitindo ainda a reutilização das classes. Primeiramente é necessário criar uma interface, que irá nos possibilitar a troca da implementação, e vamos fazer com que a nossa classe de configuração retorne a implementação desejada. Eis as alterações:
arquivo: Conexao.php
// Interface para a conexão interface Conexao { // Método que será executado na inicialização da requisição public function start(); // Método que será executado na finalização da requisição public function finish(); }
E agora transferimos a conexão do mysql para uma classe separada, que implementa a conexão:
arquivo: Conexao/Mysql.php
class Conexao_Mysql implements Conexao { public function start() { $dbname="teste"; // Indique o nome do banco de dados que será aberto $usuario="admin"; // Indique o nome do usuário que tem acesso $password="SenhaDoAdmin"; // Indique a senha do usuário mysql_connect("localhost",$usuario,$password) or dir(mysql_error()); mysql_select_db($dbname,$id) or die(mysql_error()); } public function finish() { mysql_close(); // Agora da pra colocar novamente } }
Adicionamos mais um método no Config.php para retornar a conexão desejada.
arquivo: Config.php
<?php //Arquivo de configuração do FrameWork class Config { private $commandInfo; private $conexao; public function configure() { //Configura a Classe que irá ler os parâmetros da URL $this->commandInfo = new Command_Info_Default(); // Configura nossa conexão $this->conexao = new Conexao_Mysql(); } // Retorna o CommandInfo public function getCommandInfo() { return $this->commandInfo; } public function getConexao() { return $this->conexao; } } ?>
Alterando o index para funcionar com a configuração;
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $config->configure(); // Devemos configurar antes de chamar a conexão // Recupera a conexão e inicializa ela $conn = $config->getConexao(); $conn->start(); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); // Finaliza a conexão $conn->finish(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Agora temos nosso método de conexão totalmente personalizável! Quando desejarmos personalizar nossa conexão, basta editar somente o arquivo de configuração, e pronto!
Vamos analisar mais profundamente nosso framework. Com certeza o mecanismo de conexão com o banco de dados é fundamental para qualquer aplicação, mas existem outros serviços que podem ser necessário inicializar em toda e qualquer requisição, similar a conexão (como log ou controle de acesso). O framework precisa possibilitar, de maneira fácil, a adição desses mecanismos. Para isto, basta alterar nossa interface de conexão para um nome mais genérico, afim de que ela possa tambem ser utilizada para outros fins. Vamos chama-lá de RequestAction, pois é uma classe de ações que serão realizadas em todas as conexões;
arquivo: RequestAction.php
// Interface para todo e qualquer mecanismo que deve ser executado nas requisições // É a mesma estrutura da Conexao.php interface RequestAction { // Método que será executado na inicialização da requisição public function start(); // Método que será executado na finalização da requisição public function finish(); }
E agora alteramos a conexão do mysql para implementar a interface RequestAction, em vez de Conexao. Tambem trocamos ela de diretório para ter uma melhor semântica no nome;
arquivo: RequestAction/MysqlConnection.php
class RequestAction_MysqlConnection implements RequestAction { public function start() { $dbname="teste"; // Indique o nome do banco de dados que será aberto $usuario="admin"; // Indique o nome do usuário que tem acesso $password="SenhaDoAdmin"; // Indique a senha do usuário mysql_connect("localhost",$usuario,$password) or dir(mysql_error()); mysql_select_db($dbname,$id) or die(mysql_error()); } public function finish() { mysql_close(); // Agora da pra colocar novamente } }
arquivo: Config.php
<?php //Arquivo de configuração do FrameWork class Config { private $commandInfo; private $requestAction; public function configure() { //Configura a Classe que irá ler os parâmetros da URL $this->commandInfo = new Command_Info_Default(); // Configura nossa conexão $this->requestAction = new RequestAction_MysqlConnection(); } // Retorna o CommandInfo public function getCommandInfo() { return $this->commandInfo; } public function getRequestAction() { return $this->requestAction; } } ?>
Alterando o index para funcionar com a configuração;
arquivo: index.php
try { //Inicia a configuração $config = new Config(); $config->configure(); // Recupera a RequestAction e a inicializa $reqAction = $config->getRequestAction(); $reqAction->start(); //Instancia o Factory e Recupera o Command $factory = new Command_Factory(); $factory->createCommand($config->getCommandInfo())->execute(); // Finaliza a RequestAction $reqAction->finish(); } catch (Exception_Pagenotfound $ep) { echo '<h1>ERRO 404 - Página não encontrada</h1>'; } catch (Exception $e) { echo '<h1>ERRO 500 - Erro na execução</h1>'; } ?>
Agora temos um mecanismo totalmente genérico que funciona para qualquer coisa, e não somente para conexão com o banco de dados! Caso quisermos fazer um log, é só trocar na configuração o RequestAction_MysqlConnection por uma RequestAction_Log, de forma simples e fácil sem precisarmos mecher com no código do nosso index.php.
Mas, e se quisermos o log e a conexão com o banco? Poderiamos criar uma classe chamada RequestAction_LogAndMysqlConnection, mas não é interessante pois, caso houver vários clientes ou aplicações, o seu sistema de log muito provavelmente será igual para todos. É nesse ponto que entra o nosso amigo Composite! Vamos criar uma classe que compõe vários composites, chamada RequestAction_Composite, que irá possibilitar o reaproveitamento deles. Vamos ao código:
arquivo: RequestAction/Composite.php
RequestAction_Composite implements RequestAction { private $requestActions = array(); public function add(RequestAction $reqAct) { $this->requestActions[] = $reqAct; } public function start() { foreach ($this->requestActions as $reqAct) { $reqAct->start(); } } public function finish() { foreach ($this->requestActions as $reqAct) { $reqAct->finish(); } } }
Agora no nosso config, vamos utiliza-lo para poder utilizar vários RequestActions:
arquivo: Config.php
<?php //Arquivo de configuração do FrameWork class Config { private $commandInfo; private $requestAction; public function configure() { //Configura a Classe que irá ler os parâmetros da URL $this->commandInfo = new Command_Info_Default(); // Configura os RequestActions $this->requestAction = new RequestAction_Composite(); $this->requestAction->add(RequestAction_MysqlConnection()); $this->requestAction->add(RequestAction_Log()); $this->requestAction->add(RequestAction_ControleAcesso()); // Quantos forem necessários... } // Retorna o CommandInfo public function getCommandInfo() { return $this->commandInfo; } public function getRequestAction() { return $this->requestAction; } } ?>
Agora sim podemos dar como concluída nosso mecanismo de inicialização de recursos, que nos possibilita trocar a implementação com a utilização do padrão Strategy, e também possibilita a utilização de vários mecanismos diferentes em conjunto, e a reutilização deles, com a ajuda do padrão Composite. As configurações continuam sendo feitas em um único local (config.php), mantendo o resto do código do sistema sem a necessidade de alteração.
[...] Este post foi comentado no Twitter by Rangel, Video Aulas Brasil. Video Aulas Brasil said: [dica] padrão de projeto – Composite – http://migre.me/3LOHE [...]
Primeiramente, parabéns pelos excelentes artigos. Tenho aprendido muito neste blog.
Bom, gostaria de saber se tem os fontes dessa parte 4, para baixarmos? Ou um pacote completo com o conteúdo das 4 partes do artigo?
Obrigado.
Caro Everton,
Esta série está formidável ! Terei muito o que aprender com ela.
Parabéns, um ótimo trabalho.
Muito obrigado.
Everton.
Vc conseguiu transmitir muito bem a forma de se usar Padrões de Projeto e reduzir muito o código.
Muitos desenvolvedores acreditam que os Padrões de Projeto(Design Patterns) são algo para grandes aplicações ou grandes projetos.
Mas pelo contrário, eles mantém as coisas nos seus lugares e facilitam muito a manutenção e simplicidade que os projetos necessitam ter.
Esta visão, eu só adquiri depois q comecei a utilizar os Padrões com muita freqüência e de estudar muitos outros Frameworks como Zend Framework, Cake, etc. Isto se falando de PHP, fora outros como Ruby on Rails e outros como JSF em Java.
Parabéns
Cara sua dicadica é muito boa, estou apreendendo muita coisa que não sabia, pena que você deu uma parada… queria saber mais tipos de padrões de projetos, em nossa lingua é muito dificil de achar, isso é uma pena…
Se puder dar um site para eu dar uma pesquisada, queria continuar a produção deste frame work mas acredito que falta alguns tipos de processo para terminar, por isso fico a esperar a continuação…
Rico material. Muito bom mesmo. Haverão outras partes? Outras dicas? Espero que sim.
Abraços
Também acho este tutoriais ricos em informação, mas gostaria de saber sua posição em relação em continuar ou não o projeto… Poderia nos dar umas dicas do que vira nos próximos…
Queria saber mais como o model e o view funcionara, se tivesse uma dica já iria estudando ele, abraços e não desista!
Caro Éverton,
Estou aprendendo muito com estes tutoriais. Você não tem Cursos ou Livros publicados com essa mesma abordagem sobre o tema?
Abraços
Obrigado pelo Feedback Antonio!!
Ainda não tenho livro publicado, nem curso disponível… mas quem sabe! Você tem interesse de curso presencial ou online?
=)