Back-End

2 ago, 2010

Design Patterns e o Desenvolvimento em PHP – Strategy

Publicidade

Olá,
queridos leitores da Comunidade iMasters! Sejam bem-vindos a mais uma parte da série
Design
Patterns e o Desenvolvimento em PHP. Nosso 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.

Não deixe de ler o artigo anterior! Apenas para refrescar a memória, você lembra 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 consequê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)

Problema

Muitas vezes, ao desenvolvermos uma aplicação, seja ela da natureza que for, nos deparamos com situações que requerem um pouco mais de atenção no que se refere à solução de problemas de planejamento e, principalmente, implementação.

Imaginem uma situação hipotética em que temos diversas classes e, nesse emaranhado de classes, diversas heranças. Muitas vezes necessitamos que as classes de diferentes gerações de herança tratem de maneira única algumas funcionalidades – entenda por métodos – que as classes de onde herdaram possuem.

Temos, hipoteticamente, a seguinte situação:

A solução mais simples para esse problema seria recorrer ao polimorfismo para sobrescrever os métodos das classes que vêm acima na hierarquia e definir a maneira como a classe filha implementa esses métodos.

Imaginem a seguinte situação:

abstract class Veiculo

{

public function transportar()

{

print('transportando sobre a estrada...');

}

}

class Carro extends Veiculo

{}

class Aviao extends Veiculo

{}

Esse tipo de arquitetura de implementação é bastante interessante quando temos um objetivo, digamos, limitado de aplicabilidade. Se criássemos um objeto de cada tipo, de acordo com a estrutura representada na imagem, e invocássemos o método transportar(), herdado da primeira geração dessa herança, da seguinte maneira:

$carro = new Carro();

$aviao = new Aviao();



$carro->transportar();

$aviao->transportar();

Teríamos, a partir disso, a seguinte saída:

transportando sobre a estrada...

transportando sobre a estrada...

A herança funciona muito bem para o caso de criarmos um objeto do tipo Carro, onde obtemos a mensagem herdada. Ao criarmos um objeto do tipo Aviao, todavia, a aplicação fica limitada à herança, e nos passa uma mensagem inadequada para essa situação.

Isso, porém, nos traz um problema ainda maior. A mais simples e rápida solução seria adotar o polimorfismo, reescrevendo, dessa forma, os métodos transportar() em cada nível da herança, da maneira que eles deveriam se comportar.

O grande problema dessa metodologia é que teríamos que
reescrever diversas vezes um mesmo método se quiséssemos mudar a maneira
como ele opera nas diferentes camadas de nossa aplicação. Perderíamos, assim, o sentido de termos toda a arquitetura de herança em nossa aplicação.

O que vamos fazer

  • Montar uma família de algoritmos, encapsular cada um deles e
    fazer com que todos possam alternar de posição dependendo da necessidade
    da aplicação.
  • Fazer com que os algoritmos variem de acordo com as requisições do cliente.
  • Capturar a abstração em uma interface e focar os detalhes de cada implementação nas classes que derivam dessa interface.

Solução

De modo a resolver toda a questão representada no tópico inicial do artigo, é preciso que se pense de maneira mais aberta com relação à estrutura da aplicação em seu contexto de herança, em todos os níveis de hierarquia.

A melhor solução encontrada, e assim documentada como o Design Pattern Strategy, recomenda a criação de uma variável de algoritmo flutuante que irá compor o código central da aplicação. Essa variável de algoritmo varia de acordo com a requisição do usuário.

Essas requisições são baseadas em uma base pronta de objetos capazes de se comportar da maneira requisitada pelo usuário e esperada pela estrutura hierárquica da aplicação.

O diagrama abaixo demonstra o código central da aplicação com sua variável de algoritmo, e diversos objetos que representam os diferentes algoritmos, capazes de se comportar de diferentes maneiras.

Assim, temos uma aplicação pronta para receber qualquer tipo de requisição do usuário e tratá-la da maneira esperada pelo mesmo. Da mesma maneira, temos uma estrutura pronta para receber diversos algoritmos diferentes do mesmo tipo, caracterizados por herdarem de um lugar em comum, mas com suas devidas peculiaridades.

Estrutura

No diagrama abaixo, está representada a estrutura do Strategy:

Percebe-se claramente, a partir do diagrama, que a base que irá calcar o processo da aplicação é uma interface. Todas as classes que poderão responder uma requisição serão implementações dessa interface, porém cada uma com suas devidas características únicas.

Dessa maneira, é possível obter um fluxo ideal de controle de aplicação e, ao mesmo tempo, fazer a aplicação se comportar da maneira que deve frente ao usuário requisitante.

Exemplo

Para exemplificar a solução do problema apresentado no início do artigo, vamos criar a interface MeioTransporte, que representará todo e qualquer veículo capaz de transportar alguma coisa, com suas devidas implementações:

<?php

interface MeioTransporte

{

public function transportar();

}

class Aviao implements MeioTransporte

{

public function transportar()

{

print('voando nos céus...');

}

}

class Carro implements MeioTransporte

{

public function transportar()

{

print('andando sobre a estrada...');

}

}

A partir dessa estrutura, já temos nossos diferentes algoritmos encarregados de conversar com o requisitante. Dessa forma, nosso método transportar() poderá se comportar da maneira que a aplicação decidir, sempre de acordo com a requisição feita pelo usuário.

Agora, sim, ao criarmos a abstração de Veículo, teremos uma estrutura pronta para determinar o fluxo da aplicação:

<?php

abstract class Veiculo

{

private $meioTransporte;

public function setMeioTransporte( MeioTransporte $meioTransporte )

{

$this->meioTransporte = $meioTransporte;

}

public function transportar()

{

$this->meioTransporte->transportar();

}

}

Depois de criarmos a abstração da classe Veículo, podemos criar os veículos em si, sem nos preocuparmos com a implementação do método transportar(), que já será manipulado pela estrutura que criamos anteriormente:

class Boeing extends Veiculo

{

public function __construct()

{

$this->setMeioTransporte( new Aviao() );

}

}

class Fusca extends Veiculo

{

public function __construct()

{

$this->setMeioTransporte( new Carro() );

}

}

Com essa implementação do Strategy, garantimos a organização estrutural da nossa hierarquia, e permitimos que a aplicação manipule, da maneira que a estruturamos, como será o fluxo de sua resposta à requisição.

Se criarmos, agora, um objeto de cada tipo representado na implementação, e invocarmos o método transportar(), herdado por cada um da abstração de Veículo, da seguinte maneira:

$carro = new Carro();

$aviao = new Aviao();



$carro->transportar();

$aviao->transportar();

Teríamos a seguinte saída:

andando sobre a estrada...
voando nos céus...

Fica evidenciada, a partir desse breve exemplo demonstrado nas implementações anteriores, a eficácia do padrão Strategy sobre a necessidade de adaptarmos a aplicação para todo e qualquer tipo de 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 Strategy 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!