Conhecendo Design Patterns e o padrão Strategy

Nest post falarei um pouco sobre design patterns, algo que existe há décadas, muitas universidades ignoram e consequentemente poucos desenvolvedores sabem o que são ou sabem utilizar na prática.

Eu mesmo comecei a estudar sobre o assunto há uns 2 anos e, aos poucos, fui começando a entender e a aplicar alguns dos padrões mais conhecidos.

Breve histórico

No final dos anos 70, “padrão de projeto” era um conceito da Arquitetura, descrito no livro “A Pattern Language“, do famoso arquiteto americano austríaco Christopher Alexander. Já nos anos 80, Kent Beck e Ward Cunningham começaram a aplicar a ideia de padrões na área de programação.

Porém, somente em 1995, os padrões de projeto (ou design patterns) ganharam popularidade, quando Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides publicaram o livro “Design Patterns – Elements of Reusable Object-Oriented Software“, catalogando 23 design patterns. Esses 4 autores ficaram então conhecidos como Gang of Four (GoF), que também é um termo usado para se referir aos padrões do livro (GoF Design Patterns). (Quem souber de onde surgiu o nome Gang of Four me avise!)

Os GoF Design Patterns são, atualmente, considerados a base para vários outros padrões e o livro acima se tornou um clássico, sendo citado em inúmeros artigos, palestras e outros livros.

Definição

Mas afinal, o que é um design pattern?

Um design pattern descreve uma solução geral para um problema de design que ocorre com frequência na área de desenvolvimento de software. Sendo assim, design pattern não é código pronto para reaproveitar em sua aplicação e sim um modelo para resolver um problema.

Exemplo (Padrão Strategy)

Ainda perdido? Vou tentar clarear melhor com um exemplo prático do padrão Strategy. Este padrão, além de muito popular, é bem simples de ser entendido.

O padrão Strategy serve para “definir uma família de algoritmos, encapsular cada uma delas e torná-las intercambiáveis. Strategy permite que o algoritmo varie independentemente dos clientes que o utilizam” (como definido no livro do GoF).

Em outras palavras, Strategy nos permite configurar uma classe com um de vários comportamentos, utilizando o conceito de OO chamado de composição.

A estrutura deste padrão pode ser visualizada pelo seguinte diagrama de classes:

Padrão Strategy
(figura obtida de DoFactory.com)

Imagine que, em uma aplicação de vendas, um Pedido possua duas formas diferentes (estratégias) de se calcular a taxa de entrega do mesmo: Padrão e Expresso.

Neste caso, o Pedido (Context) seria configurado por um cliente (classe que o utiliza) com a estratégia desejada. A implementação da classe Pedido poderia ser algo como (por simplicidade inclui apenas a estratégia):

public class Pedido
{
    // utilizando composição: o pedido mantém uma referência (passada em seu construtor)
    // para a Strategy base, normalmente uma interface ou classe abstrata

    private ICalculadorDeFrete _calculadorDeFrete;
    public Pedido(ICalculadorDeFrete calculadorDeFrete)
    {
        _calculadorDeFrete = calculadorDeFrete;
    }

    public decimal CalcularFrete()
    {
        // delegando o cálculo do frete para o objeto Strategy configurado
        return _calculadorDeFrete.CalcularFretePara(this);
    }
}

Abaixo escrevemos a Strategy base ICalculadorDeFrete:

public interface ICalculadorDeFrete
{
    decimal CalcularFretePara(Pedido pedido);
}

Implementamos, então, as estratégias concretas:

// frete "Padrão"
public class CalculadorDeFretePadrao : ICalculadorDeFrete
{
    public decimal CalcularFretePara(Pedido pedido)
    {
        // aqui vai o algoritmo de cálculo de frete "padrão"
    }
}

// frete "Expresso"
public class CalculadorDeFreteExpresso : ICalculadorDeFrete
{
    public decimal CalcularFretePara(Pedido pedido)
    {
        // aqui vai o algoritmo de cálculo de frete "expresso"
    }
}

Temos, assim, toda a estrutura implementada: o contexto (Pedido), a Strategy base (ICalculadorDeFrete) e as Strategies concretas (CalculadorDeFretePadrao e CalculadorDeFreteExpresso).

Dessa forma, o cliente que for utilizar o Pedido, poderá configurá-lo com o calculador desejado:

// alguma classe que utilize o Pedido
// pedido criado com o cálculo de frete padrão
var pedido1 = new Pedido(new CalculadorDeFretePadrao());
var fretePadrao = pedido1.CalcularFrete();

// pedido criado com o cálculo de frete expresso
var pedido2 = new Pedido(new CalculadorDeFreteExpresso());
var freteExpresso = pedido2.CalcularFrete();
.....
.....

Simples não é? Com isso ganhamos em flexibidade, obedecendo alguns princípios básicos da OO:

1. Programe para abstrações: notem que nosso Pedido não depende diretamente de nenhum calculador concreto e sim da interface ICalculadorDeFrete.
2. Open-closed principle: nosso Pedido não terá nenhum impacto caso o número de classes derivadas aumente (precisamos de um novo cálculo de frete) e nem se precisarmos alterar o algoritmo de algum calculador.

Vale lembrar que cada padrão pode ter variações de uma solução para outra. No exemplo dado, a estratégia adotada é passada via construtor do Pedido, mas poderia ser diretamente no método que a utiliza ou ainda, Pedido poderia implementar um método específico para “setar” a estratégia de cálculo.

Outra situação: o método CalcularFretePara recebe o próprio contexto (Pedido), o que é apenas uma alternativa. O Pedido poderia passar para o método somente os campos necessários para o cálculo do frete.

O que eu ganho com eles?

Agora que você já sabe de onde vieram, o que são e já viu um deles na prática, você pode estar se perguntando: o que eu ganho ao utilizar design patterns? Como eu sei em que situação aplicar determinado(s) padrão(ões)?

Vamos a algumas vantagens:

– Vocabulário comum entre desenvolvedores: devs que conhecem padrões podem conversar “dando nome aos bois”, ou seja, ao invés de explicar a solução contando uma história (muitas vezes confusa), pode-se dizer “utilizei o padrão X”.

– Facilitam na aplicação de técnicas e princípios de OO, como herança, composição, polimorfismo e SOLID.

– Um padrão pode ser utilizado para refatoração de um código ruim, altamente acoplado e de baixa coesão.

– Todos esses ítens colaboram para melhor facilidade de extensão e manutenção do software.

Para saber quando aplicá-los, você deve primeiramente conhecê-los e entendê-los bem (cada design pattern possui uma intenção explícita, explicando qual sua finalidade – como a citada acima no padrão Strategy). Feito isso, não se preocupe, a experiência vai te guiar e você vai naturalmente “enxergar” onde cabe determinado padrão.

Mas cuidado!

“Conhecê-los bem” e “saber onde colocá-los” não significa que eles DEVAM ser aplicados por todo o software. Design Patterns é muito legal, e por isso nos empolgamos em querer utilizá-los a qualquer custo, o que pode ser perigoso!

Não se esqueça que devemos sempre pensar simples (KISS) e não sair acrescentando código que não iremos precisar (pelo menos, em um primeiro momento) (YAGNI).

Por onde começar?

Interessou? Sugiro começar pelo ótimo Head First: Design Patterns, que é um livro muito didático. Seus exemplos estão em Java, mas como se trata basicamente de conceitos de OO, é praticamente idêntico ao C#. (Se não me engano, no site da editora pode ser feito o download dos exemplos em C#.).

O livro do GoF, mencionado no início, é um clássico, mas sua leitura é mais complicada. Por se tratar de um livro mais antigo, seus exemplos estão em C++ e há várias citações a aplicativos daquela época e ao SmallTalk (incluindo alguns códigos). Portanto, não recomendo como primeira leitura.

Um site muito conhecido sobre o assunto é o www.dofactory.com, voltado para o .Net, que possui exemplos de código em C# para todos os 23 padrões do GoF. Vale a pena conferir!

Conclusão

Design Patterns estão por aí há muito tempo e merecem ser observados com carinho. Sabendo utilizá-los, podemos melhorar muito o design de nossas aplicações. Contudo, devemos ter cuidado para não utilizá-los indiscriminadamente, complicando o que poderia ser mais simples e saindo do verdadeiro foco que é entregar software.

Acompanhe mais sobre design patterns pela tag “design patterns”.

33 comentários em “Conhecendo Design Patterns e o padrão Strategy

Adicione o seu

  1. É… é realmente um pena que a faculdades acreditem q ensinar um pouco de lógica e de análise estarão colocando profissionais capacitados no mercado. Ou pior, esses recém formados que “leram” algo sobre Patterns e acham que estão dominando o assunto, e acabam criando problemas ainda maiores de manutenção.

    Mas é isso aí, parabéns Tiozão pelo post, que estão cada vez mais alto nível!!

    Curtir

  2. Eu queria saber qual designer utilizar para essa situação. Tenho que importar arquivos, um é posicional e o outro é delimitador, é uma mesma ação só de comportamentos diferentes.

    Curtir

    1. Olá, Carlos
      Existem várias soluções para um mesmo problema. E assim, sem saber seu contexto, fica difícil ser muito preciso.
      Ao que parece, você só varia a parte da extração da linha do arquivo. Sendo assim, essa extração poderia ser abstraída e implementar um método de extração por posição e outro por delimitador. Caso de um strategy mesmo.
      É o que eu consegui pensar assim de “bate-pronto” :).
      Lembrando que você não deve deixar complexo demais seu código se já está simples o bastante como está.
      []s

      Curtir

  3. Carlos, bom tutorial.

    Porem ainda sim este exemplo não me abriu a mente para este pattern. Teria a possibilidade de você criar um exemplo utilizando o seguinte cenario: Pessoa, Pessoa Fisica, Pessoa Juridica.

    Abraço.

    Curtir

      1. Tranquilo, Jhonathan.

        O padrão Strategy utiliza-se do conceito de composição. A classe contexto (no meu exemplo, o Pedido) USA a strategy para variar o comportamento (ao invés de usar herança: Pedido, PedidoComFretePadrao e PedidoComFreteExpresso). Assim tenho um código menos acoplado e mais flexível.

        Pelo exemplo que deu (não foi um cenário! :)), você quer fazer uma herança certo? Qual a finalidade? Herdar dados? Sem um contexto não tem com aprofundar…O Strategy nos permite variar COMPORTAMENTO (uma operaçao que seu objeto delega para outro) de forma extensivel, sem termos que ficar alterando a classe contexto quando surgir uma nova estratégia.

        Qquer coisa, responde aí que a gente tenta aprofundar mais.
        []s

        Curtir

    1. Nao, “alguem”. Nem sequer consumo material deles há anos.
      Se puder ajudar, coloque aí o link do artigo ao qual se refere porque, sendo cópia do meu, entrarei em contato com o “autor”.
      E obrigado pelo pageview e comentário.

      Curtir

  4. Olá Robson, desculpe mas eu sou bem iniciante, e fiquei intrigado com esse trecho de código, aonde você usa o “:” em frente ao nome da classe:

    public class CalculadorDeFretePadrao : ICalculadorDeFrete

    os dois pontos fazem a vez do implements para indicar que a classe CalculadorDeFretePadrao é uma implementação de ICalculadorDeFrete?

    Curtir

  5. Olá, parabéns pelo post, eu tenho uma duvida por ex:
    Tenho 3 softwares e preciso gerar serial para eles.

    A logica para criar o serial é:
    int serial = nome.length * ID1 * ID2;

    Só que cada software os id1 e id2 mudam.

    Se eu usar o estrategy teria que repetir a logica diversas vezes ex:

    public interface ICalculadorDeSerial
    {
    decimal CalcularSerialPara(Programa programa);
    }
    // programa 1
    public class CalculadorDeSerialProgram1 : ICalculadorDeSerial
    {
    public int CalcularSerialPara(Programa programa)
    {
    return int serial = programa.getTamNome() * 10 * 20; //repete 1x
    }
    }

    // programa 2
    public class CalculadorDeSerialProgram2 : ICalculadorDeSerial
    {
    public int CalcularSerialPara(Programa programa)
    {
    return int serial = programa.getTamNome() * 40 * 50; //repete 2x
    }
    }

    // programa 3
    public class CalculadorDeSerialProgram2 : ICalculadorDeSerial
    {
    public int CalcularSerialPara(Programa programa)
    {
    return int serial = programa.getTamNome() * 70 * 90; //repete 3x
    }
    }

    se tivesse mais programas ia repetir mais ainda, qual a melhor forma de fazer isso?

    Obrigado.

    Curtir

    1. Oi, Rafael
      Perdão pela demora.

      A ideia do Strategy é você encapsular uma familia de algoritmos de modo que o cliente possa utilizar qquer um deles.

      Fica impossível dar uma solução sem entender direito o problema.

      Quem é o cliente (classe) do ICalculadorDeSerial? É a classe Programa?

      O que são os IDs? Quem os fornece? Pelo que você mostrou, o algoritmo não está variando. As 3 strategies concretas possuem a mesma lógica, o que varia são esses IDs. Então, está me parecendo sem sentido a existência das 3…

      Qualquer coisa, retorna ai com essas respostas que tentamos chegar a uma solução.
      []s

      Curtir

      1. Obrigado pelo o retorno, ja consegui fazer, usei uma interface e implementei nas classes, como o id era fixo eu instanciei nas classes dos programas e deu sucesso,

        Mais uma vez obrigado pelo post, foi de grande ajuda.

        Abraços.

        Curtir

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Blog no WordPress.com.

Acima ↑