C# -  Padrão Comportamental Gof - Strategy


 Neste artigo vou apresentar o padrão comportamental Gof Strategy.

O padrão de design Strategy define uma família de algoritmos, encapsula cada um e os torna intercambiáveis.

Vamos entender o que isso significa:

- Família de algoritmos:
Isso significa que este padrão fornece um conjunto de algoritmos usando um dos quais em tempo de execução você pode obter a saída desejada.

- Encapsula cada um :
Este padrão permite que você coloque seus algoritmos em classes diferentes (encapsulando-os)

- Torna o algoritmo intercambiável:
A beleza do padrão Strategy é que podemos selecionar em tempo de execução qual algoritmo queremos aplicar ao nosso objeto e também substituí-los um pelo outro.



Dessa forma esse padrão permite que o algoritmo varie independentemente dos clientes que o utilizam. E assim podemos dizer que o objetivo deste padrão é encapsular um algoritmo em um objeto e fornecer interfaces genéricas o suficiente para suportar uma variedade de algoritmos e facilitar a escolha e troca (intercâmbio) de algoritmos criados com uma mesma função. 

Este padrão é frequentemente usado em várias estruturas para fornecer aos usuários uma maneira de alterar o comportamento de uma classe sem estendê-la.

Strategy : Exemplo

Vejamos um exemplo simples para entendermos a atuação do padrão.

Vamos supor que temos uma tarefa a resolver  :




E que descobrimos que podemos ter 3 soluções para resolver a tarefa:  Solucão1 , 2 e 3

Em cada solução temos representados um comportamento e um algoritmo diferente. Aqui temos a família de algoritmo encapsulada cada um em sua classe.

De acordo com o padrão Strategy,  a solução que deverá ser usada  será decidida pelo cliente apenas em tempo de execução.  Assim, o cliente decidirá se usará a Solução 1 , 2 ou 3 para realizar a tarefa em tempo de execução.

Então temos que as soluções que representam a família de algoritmos encapsuladas cada uma em sua classe
podem ser intercambiáveis e podemos selecionar qual usar em tempo de execução substituindo um pelo outro.

Tomando um exemplo do mundo real, temos que os meios de transporte para um aeroporto são um exemplo do padrão Strategy ou Estratégia.

Existem várias opções de transporte que podem ser usadas neste caso :

- pegar um ônibus;
- usar um carro;
- pegar um táxi;



Qualquer um desses meios de transporte leva o viajante ao aeroporto e pode ser usado de forma intercambiável.

O viajante deve escolher a Estratégia com base nas compensações entre custo, conveniência e tempo.

Diagrama UML

O diagrama UML do padrão Strategy segundo o Gof apresenta os seguintes participantes

1- Strategy

- Define a Interface comum para todas as classes (variações concretas) que definem os diversos comportamentos esperados;
- O Context usa esta interface para chamar o algoritmo definido por um ConcreteStrategy;

2- ConcreteStrategy -

- Classes que implementam os algoritmos que devem atender a cada contexto;

3- Context

-Classe onde os objetos ConcreteStrategy serão instanciados;
-Mantém uma referência para o objeto Strategy

Assim temos aqui:

- Uma interface chamada Strategy que define um contrato a ser implementado;
- As classes de estratégia concretas A,B e C que implementam essa interface;
- Por último, a classe Context através da qual podemos usar diferentes estratégias concretas em tempo de execução.

As classes Context instanciam os objetos Strategy e invocam o método AlgorithmInterface passando os parâmetros solicitados;

A interface Strategy decide qual das implementações ConcreteStrategy deve atender a chamada;

Quando podemos usar o padrão

Podemos usar o padrão Strategy quando :

- Muitas classes relacionadas diferem apenas em seu comportamento;
- Precisamos usar diferentes variantes de um algoritmo;
- Um algoritmo usa dados que os clientes não deveriam conhecer; Neste caso usamos o padrão Strategy para evitar a exposição de estruturas de dados complexas e específicas do algoritmo.
- Uma classe define muitos comportamentos e eles aparecem como várias instruções condicionais em suas operações;
Aqui em vez de muitos condicionais, podemos mover os condicionais relacionados para sua própria classe de Estratégia.
- Você tem um método que é aplicado em diferentes situações nas quais é exigido um comportamento específico;

Vantagens do padrão

Como vantagens deste padrão temos que :

-Facilita e simplifica os testes de unidade pois cada algoritmo tem sua classe e pode ser testado pela sua própria interface;
- Evita o uso de condicionais, tornando o código mais flexível e fácil de estender;
- É aderente aos princípios de alta coesão e baixo acoplamento;
- Orienta a programar para uma interface e usar a composição;

Desvantagem

E como desvantagem podemos citar que :

- Ocorre um aumento no número de classes;
-A aplicação deve estar ciente de todas as estratégias para poder selecionar a estratégia certa para a situação certa;

Aplicação prática do padrão

Para mostrar um exemplo de implementação deste padrão vamos supor que eu quero poder compactar arquivos, e, para isso eu tenho 3 soluções que são as estratégias que eu posso usar para realizar essa tarefa.

Assim eu posso compactar arquivos usando o formato Rar, o formato ZIP e o formato GZip.



Assim vamos usar o padrão Strategy para definir como podemos implementar 3 algoritmos de forma que possamos escolher o formato em tempo de execução.

Implementação prática

Levando em conta este cenário vamos implementar o padrão Strategy usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.

A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:

A seguir temos o código usado na implementação:

1- A interface ICompressao

    //Strategy
    public interface ICompressao
    {
        void ComprimirArquivo(string nomeArquivo);
    }

2- A classe CompressaoContext

     //Context
    public class CompressaoContext
    {
        private ICompressao _icompressao;
        public CompressaoContext(ICompressao icompressao)
        {
            _icompressao = icompressao;
        }
        public void DefineStrategy(ICompressao icompressao)
        {
            _icompressao = icompressao;
        }
        public void CriarArquivoCompactado(string nomeArquivo)
        {
            _icompressao.ComprimirArquivo(nomeArquivo);
        }
    }

 

3- Classe CompressaoZip

    public class CompressaoGzip : ICompressao
    {
        public void ComprimirArquivo(string nomeArquivo)
        {
            Console.WriteLine($"\nO arquivo {nomeArquivo} foi compactado usando GZip " +
               $"\nUm arquivo com extensão .gzip foi criado");
        }
    }

4- Classe CompressaoGzip

    public class CompressaoZip : ICompressao
    {
        public void ComprimirArquivo(string nomeArquivo)
        {
            Console.WriteLine($"\nO arquivo '{nomeArquivo}' foi compactado usando Zip " +
               $"\nUm arquivo com extensão .zip foi criado");
        }
    }

5- Classe CompressaoRar

    public class CompressaoRar : ICompressao
    {
        public void ComprimirArquivo(string nomeArquivo)
        {
            Console.WriteLine($"\nO arquivo '{nomeArquivo}' foi compactado usando Rar. " +
                $"\nUm arquivo com extensão .rar foi criado");
        }
    }

7- Program

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("## Padrão Stratagey ##\n");
            CompressaoContext ctx = new CompressaoContext(new CompressaoRar());

            Console.WriteLine("Informe o nome do arquivo a comprimir");
            var nomeArquivo = Console.ReadLine();

            Console.WriteLine("\nInforme o tipo de compressão a ser usada");
            Console.WriteLine("1-Rar(Padrão) 2-Zip 3-Gzip\n");
            var opcao = Convert.ToInt32(Console.ReadLine());
            if (opcao == 2)
            {
                ctx.DefineStrategy(new CompressaoZip());
            }
            else if (opcao == 3)
            {
                ctx.DefineStrategy(new CompressaoGzip());
            }
            ctx.CriarArquivoCompactado(nomeArquivo);
            Console.Read();
        }
  }

 

A execução do projeto irá apresentar o seguinte resultado:

Desta forma com o padrão Strategy é possível reutilizar o código por todo o projeto.

Pegue o código do projeto aqui :
  StrategyExemplo.zip

"Bendito seja o Deus e Pai de nosso Senhor Jesus Cristo, Pai das misericórdias e Deus de toda consolação,
que nos consola em todas as nossas tribulações, para que, com a consolação que recebemos de Deus, possamos consolar os que estão passando por tribulações."
2 Coríntios 1:3,4

Referências:


José Carlos Macoratti