[Delphi] Design Patterns GoF – Strategy

Olá, leitores do Delphi!
O 21º artigo da série sobre Design Patterns refere-se ao padrão de projeto Strategy. A proposta deste padrão, apesar de simples, é bastante conveniente para situações em que é necessário alterar o comportamento de um mesmo objeto em tempo de execução, adquirindo uma nova “estratégia” para obter um resultado.
Não ficou muito claro? Acompanhe o artigo para conhecê-lo melhor!

Introdução

Em função dos requisitos dos clientes, infraestrutura ou características técnicas, algumas vezes é necessário implementar diferentes rotinas que alcancem resultados semelhantes. Tome, como exemplo, as três formas de percorrer uma lista que publiquei há alguns meses. Podemos utilizar um For com um contador, um For-In, ou um Enumerator, porém, as três formas chegam a um mesmo resultado: acessar cada item da lista para consumir suas propriedades.

Para já entrarmos no conceito do Strategy, podemos classificar essas diferentes formas como “estratégias” de execução. Mesmo assim, em algumas literaturas sobre Design Patterns, encontrei um termo ainda mais adequado: algoritmo. O objetivo do Strategy, portanto, é permitir que uma aplicação selecione e use um algoritmo em tempo de execução de acordo com condições impostas pela regra de negócio.

Analogias

Minha esposa, mais uma vez, pensando nas melhores analogias, encontrou uma que reflete bem a ideia do Strategy, referente à logins com múltiplas contas. Você certamente já deve ter utilizado algum serviço na web que permite o login utilizando a conta do Facebook, Google+ ou Twitter, não é? Os sites Pinterest, Digg e 4Shared são exemplos. A API destes provedores possuem diferentes métodos e parâmetros para acessar os dados do usuário, mas, em geral, o objetivo é o mesmo: utilizá-los para conectar o usuário ao serviço.

Exemplo de tipos de login para acessar um serviço

A imagem acima é do Digg. Podemos imaginar que cada opção corresponde a um algoritmo para efetivar o login (prefiro utilizar a palavra “imaginar” para não afirmar que o site realmente utiliza o Strategy). De acordo com a escolha do usuário, a aplicação seleciona um dos algoritmos, executa suas regras específicas para obter os dados do usuário e finalmente fornece acesso ao site. Com o Strategy, todo este procedimento acontece de forma encapsulada. O objeto que solicita os dados do usuário não conhece, de antemão, qual o provedor selecionado. Tal informação é concedida apenas em tempo de execução.

Formas de pagamento também são uma analogia. Podemos, por exemplo, optar por pagar uma compra com cartão de crédito, débito ou voucher. Cada uma dessas formas dispara uma rotina diferente na aplicação, mas com o objetivo comum de liquidar o valor da compra.

Exemplo de codificação do Strategy

O conceito do Strategy ficará mais claro com um exemplo prático. Codificaremos um validador de endereços de e-mail que atende ao perfil de diferentes tipos de usuário, descritos a seguir.

O primeiro tipo de usuário, o preenchimento de e-mails não é tão frequente, portanto, a aplicação apenas carregará uma DLL dinamicamente (que eu já desenvolvi previamente para esse exemplo) para validar o endereço de e-mail.

O segundo tipo de usuário solicita uma validação mais assertiva. Para isso, acessaremos o WebService do RegExLib para buscar uma expressão regular e validar o endereço de e-mail utilizando o record TRegEx nativo do Delphi.

O terceiro tipo de usuário exige que a validação seja feita por um serviço exclusivo para essa finalidade. Neste caso, faremos uso do MailBoxLayer, que disponibiliza uma API REST para notificar se o endereço de e-mail informado na requisição é válido.

Formulário para exemplo do Design Pattern Strategy

Observe que, apesar de termos três algoritmos, o objetivo é o mesmo. A diferença é que podemos alternar o algoritmo de validação em tempo de execução, a qualquer momento, sem que seja necessário modificar o código e recompilá-lo.

Interface Strategy

Começaremos pela Interface Strategy, que introduz os métodos que serão executados por cada algoritmo. No nosso cenário, haverá apenas uma função, responsável por receber o endereço de e-mail como parâmetro e retornar um boolean para indicar se é válido.

Classe Concrete Strategy

Prosseguindo, precisamos modelar as classes Concrete Strategy, que implementam a Interface acima. Cada uma delas executará um algoritmo específico para validar o endereço de e-mail.

O primeiro Concrete Strategy carrega uma DLL chamada “ValidadorEmail.dll” dinamicamente com o método LoadLibrary. Em seguida, obtém o endereço do método ValidarEmail para executá-lo:

O segundo Concrete Strategy é um pouco mais complexo. Precisamos importar o endereço WSDL do RegExLib através do WSDL Importer do Delphi para criar uma unit com os métodos disponibilizados pelo WebService. Feito isso, instanciaremos um componente THTTPRIO para consumir o método que busca uma expressão regular pelo ID.

No site do RegExLib, existe uma série de expressões regulares para diferentes finalidades. Cada uma delas possui um ID único para que seja possível consultá-la pelo WebService sem o risco de ambiguidades. Após analisar as expressões regulares referentes à e-mail, optei pela expressão nº 3122.

Por fim, utilizaremos o record TRegEx nativo do Delphi para confrontar o endereço de e-mail com a expressão regular:

O último Concrete Strategy encaminha a validação para um serviço na web, chamado MailBoxLayer. Para enviar uma requisição à API REST deste serviço, utilizamos o componente TIdHTTP. Depois, recebemos a resposta em um objeto da classe TJSONObject para obter o valor da chave “format_valid”. Caso seja “true”, significa que o endereço de e-mail é válido.

Bom, as nossas “estratégias” estão prontas, pessoal!

Classe Context

Para finalizar, falta apenas o terceiro elemento do Strategy, chamado Context (sim, o mesmo nome que existe no State!). Essa classe será encarregada de instanciar e manter uma referência a uma das classes Concrete Strategy para, posteriormente, chamar o método de validação de e-mail correspondente.

Para a criação do Concrete Strategy em tempo de execução, julguei oportuno declarar as opções de algoritmo como tipos enumerados:

No entanto, em um ambiente real, recomendo a implementação de uma Factory para esse propósito.

O Context disponibilizará apenas um método chamado ValidarEmail, no qual encaminhará a ação ao Concrete Strategy selecionado. Esse encapsulamento evita que o Client (classe ou formulário que consumirá o Strategy) tenha conhecimento da implementação interna das classes Concrete Strategy.

Em ação!

Vamos avaliar tudo isso funcionando na prática?

No formulário, precisamos criar uma instância do Context e, claro, também liberá-la da memória no destrutor:

No evento de validação do e-mail (botão “Validar”), o código será pequeno, simples e prático:

Perfeito, não? 🙂

Conclusão

Um dos pontos mais interessantes desse padrão de projeto é a capacidade de permutar os algoritmos em tempo de execução, permitindo que diferentes rotinas sejam executadas em um mesmo contexto de modo abstrato e desacoplado.

Além disso, não posso deixar de mencionar a facilidade de manutenção proporcionada por essa arquitetura. Para adicionar um novo algoritmo, basta apenas criar um novo Concrete Strategy e ajustar o Context para instanciá-lo quando necessário. E digo mais: como os algoritmos estão em classes separadas, significa que satisfazemos o Princípio de Responsabilidade Única. Caso você ainda não o conheça, continue acompanhando o blog! 😀

O projeto de exemplo deste artigo (com algumas melhorias) está disponível para download no link abaixo:

 

Volto em breve com o próximo Design Pattern.
Grande abraço, leitores!


 

André Celestino