Quando falamos de Design Patterns, fica muito nebuloso entender o que podemos fazer com eles, pois o que muitos desenvolvedores não compreendem é que os padrões resolvem problemas de código ou de design já identificados ou simplesmente melhoram a manutenibilidade do projeto. Para que isso aconteça primeiro precisamos passar pelo problema, conhecer o que o padrão resolve e enfim aplicar uma refatoração.
Vamos falar neste artigo sobre o Strategy, que é um padrão comportamental, muito usado para quando temos regras de uma determinada atividade que podem conter muita lógica, ele organiza e separa o uso dessas lógicas, padronizando a usabilidade das classes de forma que novas implementações possam ser adicionadas no futuro sem muita mudança no uso de uma determinada classe de ação.
Para ilustrá-lo usaremos a refatoração, veremos o problema e aplicaremos o padrão resolvendo uma problemática específica. Para que fique menos complexo tudo será demonstrado em código e comentários, assim acredito que chegaremos a um entendimento melhor.
Curso Laravel - Blade Templates
Conhecer o cursoProblemática
Temos um sistema de gestão de logística e precisamos comunicar aos cliente quando o produto chegou na distribuidora, quando está a caminho da entrega e por fim o momento em que ele foi recebido pelo cliente.
Para tratar das mensagens que são enviadas aos clientes, foi criada uma classe Message da qual comunica por e-mail os clientes, veja abaixo a representação:
class Message
{
// ...
public function send(string $message)
{
// implementação de mensagem por email
}
}
// ...
$message->send('Seu produto está a caminho da entrega');
Com o evoluir da aplicação foi preciso implementar um novo modelo de mensagem, o SMS, pois alguns clientes não possuíam cadastro online, e o sistema só possui cadastro simples como: o nome, telefone e endereço.
Resolvemos então criar duas classes, seguindo o principio de SOLID, single responsibility, a classe EmailMessage e a SMSMessage, pensando em futuras implementações fizemos a classe MessageManager retornar o objeto de acordo com o tipo de mensagem necessária.
class MessageManager
{
// ...
public function getSender()
{
if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
return new EmailMessage;
}
if ($this->customer->hasEmail() === false) {
return new SMSMessage;
}
// ...
}
}
// ...
$sender = $messageManager->getSender();
$sender->send('Seu produto está a caminho da entrega');
Mas como nada é perfeito, uma nova forma de comunicação entrou no projeto, os desenvolvedores precisam agora de um novo modo de envio, este agora via push notification, ou seja, a notificação será via aplicativo de celular, da forma atual nos forçou a ter essa implementação:
class MessageManager
{
// ...
public function getSender()
{
if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
return new EmailMessage;
}
if ($this->customer->hasEmail() === false) {
return new SMSMessage;
}
if ($this->customer->hasCellPhone()) {
return new PushMessage;
}
// ...
}
}
// ...
Curso Nginx - Criação de sites
Conhecer o cursoUma solução
Claramente não estamos seguindo corretamente a responsabilidade única, a classe MessageManager está com muitas regras de mensagem que se mesclam entre si, observamos que as regras de cada classe de mensagem deve estar dentro delas mesmas e o manager só deve invocar o objeto pedido no momento, pois ainda que surjam novas formas de mensagens, seria muito doloroso dar manutenção e testar esse método getSender().
Com o Strategy transformamos cada regra em sua classe, que deve ser passada pelo desenvolvedor, elas seguirão uma interface:
interface MessageSenderInterface
{
// ...
public function send(string $message) : bool;
public function isValidSender() : bool;
}
// ...
Essa interface fará o contrato das estratégias para que todas sigam esses métodos, implementaremos uma das classes de mensagens adicionando Strategy no nome original:
class EmailMessageStrategy implements MessageSenderInterface
{
// ...
public function send(string $message) : bool
{
// Valida de acordo com a regra deste tipo de mensagem
if ($this->isValidSender()) {
return $this->sendMessage($message);
}
return false;
}
public function isValidSender() : bool
{
if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
return true;
}
return false;
}
}
// ...
Isso deve ser feito também para as demais classes de mensagem, essa prática isola as regras/validações nas classes Strategy de mensagens. Agora faremos o MessageStrategy, que invocará o método da classe em que lhe for informado. Abaixo a implementação do padrão:
class MessageStrategy implements MessageSenderInterface
{
// ...
public function __construct(MessageSenderInterface $sender)
{
// Guardamos o objeto Message com suas regras e implementações
$this->sender = $sender;
}
public function send(string $message) : bool
{
// Retorna a validação de acordo com a regra do objeto
return $this->sender->send($message);
}
public function isValidSender() : bool
{
return $this->sender->isValidSender();
}
}
// ...
// Ao criar o objeto MessageStrategy devemos informar qual o tipo de envio
$message = new MessageStrategy(new EmailMessageStrategy);
// O método invoca o send original da classes anteriormente adicionada
$message->send('Seu produto está a caminho da entrega');
Pronto! Agora a implementação é injetada no MessageStrategy podendo haver outras implementações sem precisar alterar a classe MessageStrategy e, o melhor: as regras específicas para cada modelo de mensagem estarão em suas respectivas classes.
Conclusão
A vantagem em usar esse padrão está em facilitar a organização das classes, centralizar a usabilidade e além disso também dar poder ao desenvolvedor de adicionar camadas na estratégia principal que será replicada a todas as outras estratégias que são injetadas.
Espero que tenham gostado do artigo, a ideia foi ilustrar uma forma de implementar o padrão Strategy de forma simples, lembrando que existem várias maneiras de implementá-lo e os exemplos apresentados acima são meramente uma prova de conceitos.
Até a próxima!