Back-End

23 jul, 2010

Design Patterns e o Desenvolvimento em PHP – Chain of Responsibility

Publicidade

Olá,
queridos leitores da Comunidade iMasters! Sejam bem-vindos à série
Design
Patterns e o Desenvolvimento em PHP,
cujo objetivo é desmitificar o conceito de padrão de projeto e
apresentar casos de uso em situações reais com as quais os
desenvolvedores do mercado deparam no decorrer de seu dia-a-dia.

Definição

O
que são design patterns?

“Um
padrão de design descreve o problema, uma solução para o problema,
que consiste em um arranjo geral de objetos e classes, o momento de
aplicar a solução e as conseqüências da aplicação da solução.” (Gamma,
E., Helm, R., Johnson, R., Vlissides, J., Design
Patterns: Elements of Reusable Object-Oriented Software,
Addison-Wesley Professional; 1995)

Em
projetos de software orientados a objeto, manter os objetos com fraco
acoplamento, ou seja, especificando de maneira mínima a
responsabilidade entre cada objeto, faz com que possíveis mudanças
na arquitetura do projeto sejam postas em prática com menos riscos de
inconsistência e falhas.

Como
podemos aplicar Design Patterns?

  • Grande
    parte dos objetos podem tratar quaisquer tipos de requisição sem
    precisar ser reconhecido antecipadamente.
  • Deve
    ser especificado dinamicamente o conjunto de objetos capaz de
    manipular uma determinada requisição.

Nossos objetivos:

  • Não
    conhecer antecipadamente os objetos que respondem a determinadas
    requisições.
  • Criar
    uma corrente de fluxo contínuo com os objetos recebedores e mover a
    requisição por essa corrente até que um objeto a manipule.
  • Evitar
    o contato direto entre o objeto solicitador e o objeto receptor,
    assim concedendo a oportunidade a todos os objetos pertencentes à
    corrente de tratarem a determinada requisição.

Problema

Existe
uma potencial quantidade variável de manipuladores ou elementos
receptores na arquitetura de um sistema, e uma infinidade de
requisições que precisam ser tratadas e respondidas. O problema é
processar essas requisições de maneira eficiente sem precisar
“enrolar” relações entre os objetos e transformar o sistema em
um caos processual.

Solução

Encapsular
os elementos do processo em uma corrente de abstração; fazer, dessa
maneira, com que os clientes requisitantes
da arquitetura do sistema enviem e recebam sinal a partir da entrada
única da corrente.

Esse
padrão acorrenta os objetos receptores em uma estrutura onde eles
possam coexistir, e transmite qualquer tipo de requisição de um
objeto ao outro, até que aquela alcance um objeto capaz de
manipulá-la e enviá-la de volta ao requisitante. A
quantidade e o tipo de receptores não são conhecidos
antecipadamente,
eles devem ser configurados dinamicamente. Esse mecanismo se utiliza
da composição recursiva para permitir um número ilimitado de
receptores podendo coexistir conectados.

Chain
of Responsibility simplifica
a interrelação de objetos.
Em vez de todos os requisitantes e receptores manterem referência
para todos os possíveis candidatos a receptores, cada requisitante
mantém a referência para a “cabeça” da corrente, e cada
receptor mantém a referência para seu sucessor imediato na
hierarquia da corrente.

Estrutura

As
classes derivadas sabem como solucionar as requisições feitas pelo
cliente. Se o objeto que está sendo manipulado no momento da
requisição não estiver disponível ou não for suficiente para
respondê-la, então ele delega à classe base, que delega ao próximo
objeto candidato, e o ciclo continua. Múltiplos
manipuladores podem contribuir para a manipulação de cada
requisição.

Pelo
diagrama, podemos perceber a presença de um cliente requisitante
(Client) no momento em que está fazendo uma requisição ao sistema.
Composto por diversos (a este ponto desconhecidos) possíveis
manipuladores dessa requisição, o sistema delega a mesma à porta
de entrada da corrente (Handler), que será o responsável por
iniciar o ciclo de transferência. O primeiro candidato possível
para atender a requisição (ConcreteHandler1) tenta fazê-lo. Caso
esteja indisponível ou não seja capaz de respondê-la ao
requisitante, invoca o chamado (handleRequest()) para o próximo
possível candidato, de acordo com a ordem definida pela corrente. O
ciclo se segue até que algum objeto consiga atender a requisição,
ao ponto de retornar a resposta ao requisitante.

A
utilização do padrão Chain
of Responsibility
é uma das mais simplificadas no meio dos padrões de projeto
existentes. Pelo fato de ela existir com a finalidade de resolver um
problema bastante comum e decorrente em diversos projetos de
software, sua solução foi aprimorada a ponto de não desafiar o
desenvolvedor a aplicá-la, livrando-o, assim, de um novo problema de
projeto.

Exemplo

É
claro que na teoria tudo funciona perfeitamente. A questão é saber
se a implementação é possível de ser feita e se o projeto ficará
consistente após isso. Para melhor exemplificar a utilização desse
padrão, vamos a um exemplo bastante simples e didático:

Temos
aqui a implementação do Handler principal, a chamada “porta de
entrada” para a nossa “corrente” de fluxo contínuo que atuará
em nossa aplicação:

Handler.class.php

abstract class Handler

{

abstract public function handleRequest($request);

abstract public function setSuccessor($nextVehicle);

}

Abaixo
a implementação do primeiro receptor em nossa “corrente”:

Carro.class.php

class Carro extends Handler
{
private $successor;
public function setSuccessor($nextVehicle)
{
$this->successor = $nextVehicle;
}
public function handleRequest($request)
{
if($request->getWeight() < 1500) {
print('Um carro é o suficiente para locomover: ' . $request->getWeight() . '<br />');
} else if(!is_null($this->successor)) {
$this->successor->handleRequest($request);
}
}
}

Nosso
segundo candidato a receptor:

Caminhonete.class.php

class Caminhonete extends Handler
{
private $successor;
public function setSuccessor($nextVehicle)
{
$this->successor = $nextVehicle;
}
public function handleRequest($request)
{
if($request->getWeight() < 3000) {
print('Uma caminhonete é boa para locomover: ' . $request->getWeight() . '<br />');
} else if(!is_null($this->successor)) {
$this->successor->handleRequest($request);
}
}
}

O
terceiro e último candidato a manipular a(s) requisição(ões):

Caminhao.class.php

class Caminhao extends Handler
{
private $successor;
public function setSuccessor($nextVehicle)
{
$this->successor = $nextVehicle;
}
public function handleRequest($request)
{
if($request->getWeight() < 5000) {
print('Um caminhão será bom para locomover o peso: ' . $request->getWeight() . '<br />');
} else if(!is_null($this->successor)) {
$this->successor->handleRequest($request);
}
}
}

Aqui
se dá a implementação da Requisição em si. Não há nada muito
complexo nesta estrutura, e, evidente, é totalmente adaptável às
suas necessidades:

Request.class.php

class Request
{
private $weight;
public function Request($weight)
{
$this->weight = $weight;
}
public function getWeight()
{
return $this->weight;
}
}

E,
por último, a implementação do nosso Cliente, a peça chave para o
funcionamento do sistema, o requisitante:


Client.class.php

class Client
{
public function Client()
{
$carro = new Carro();
$caminhonete = new Caminhonete();
$caminhao = new Caminhao();

$carro->setSuccessor($caminhonete);
$caminhonete->setSuccessor($caminhao);

// Gera e processa o carregamendo das requisições
$request = new Request(1000);
$carro->handleRequest($request);

$request = new Request(2500);
$carro->handleRequest($request);

$request = new Request(4500);
$carro->handleRequest($request);
}
}

Vamos
à execução do programa:


Executa.php

ERROR_REPORTING(E_ALL);

include_once 'Carro.class.php';
include_once 'Caminhao.class.php';
include_once 'Caminhonete.class.php';
include_once 'Client.class.php';
include_once 'Handler.class.php';
include_once 'Request.class.php';

$chain = new Client();

Ao
instanciarmos um objeto do tipo Client, teremos a seguinte saída:

Um carro é o suficiente para locomover: 1000
Uma caminhonete pode é boa para locomover o peso: 2500
Um caminhão será bom para locomover o peso: 4500

A
partir da saída em tela, percebe-se claramente a atuação em
conjunto dos manipuladores do tipo Handler em uma sequência lógica.
Por
definição,
em nosso programa de exemplo, um carro pode locomover tranquilamente
um peso abaixo de 1500. Ao receber uma requisição com um valor cima
deste, o objeto se dá conta de sua impossibilidade de manipular a
requisição, e a transfere para o próximo possível candidato para
realizar tal tarefa: o próximo manipulador, um objeto do tipo
Caminhonete. Este, por sua vez, poderá locomover cargas que o objeto
do tipo Carro não pôde, mantendo assim o fluxo contínuo da
aplicação.

Se, no entanto, esse objeto receber uma carga maior que
2500, como definido
previamente
por nossa aplicação, ele mesmo perceberá que não poderá
manipulá-la, e a passará adiante na fila da “corrente”, como
feito na situação anterior. Assim, o próximo candidato à
manipulação da requisição é um objeto do tipo Caminhao, que
poderá tranquilamente manipular essa requisição.

Espero,
honestamente, que este artigo tenha trazido luz aos estudos de vocês,
leitores, na área de Padrões de Projeto. Aproveitem para testar a
metodologia do Chain
of Responsibility nos
sistemas de vocês.

Lembrem-se: um padrão de projeto é um estudo
para a solução de um problema,
e não a implementação dessa solução. Cabe a vocês implementarem
da maneira que o projeto demandar.

Nos
vemos nos próximos artigos sobre Design Patterns. Um abraço!