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:
(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”.
É… é 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!!
CurtirCurtir
Parabéns pelo post. Ficou excelente. Nota 1000. vlw
CurtirCurtir
Obrigado.
[]s e volte sempre! 🙂
CurtirCurtir
Muito bom Robson, parabéns, excelente post.
CurtirCurtir
Obrigado, Ricardo.
[]s
CurtirCurtir
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.
CurtirCurtir
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
CurtirCurtir
Seus posts são excelentes.
Comecei lendo os princípios SOLID e estou aproveitando todo o restante também.
Parabéns Robson!
CurtirCurtir
Opa, Jonatan.
Muito obrigado pelo feedback. Espero continuar ou até aumentar o nível dos posts.
[]s
CurtirCurtir
Opa amigo, excelente post.
Sobre a sua pergunta no inicio do post, acredito que gang of four seja uma jogada por ser 4 autores mesmo.
Abraço e parabéns!
CurtirCurtir
Olá, Diego.
Era o nome de um banda e também o nome de um grupo político da China. Enfim…
Obrigado pela visita!
[]s
CurtirCurtir
Parabéns Robson!!
Encontrei o que a muito tempo tinha dúvidas.
Valeu!!
CurtirCurtir
Que bom, cara.
Obrigado e volte sempre 🙂
[]s
CurtirCurtir
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.
CurtirCurtir
Ops: É Robson rsrsrsrsrsrsrs
CurtirCurtir
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
CurtirCurtir
Foi a melhor referência entre as que vi até agora.
CurtirCurtir
Obrigado, Rafael!
Keep on learning!
CurtirCurtir
copiou do devmedia né
CurtirCurtir
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.
CurtirCurtir
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?
CurtirCurtir
aaaaaaaaaa viajei Robson, foi mal, é código C# não java…. desculpa mano
CurtirCurtir
Tranquilo, mano!
CurtirCurtir
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.
CurtirCurtir
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
CurtirCurtir
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.
CurtirCurtir
Que bom, Rafael!
Obrigado você.
[]s
CurtirCurtir
Muito bons seus posts principalmente a saga a respeito do SOLID, continue com o bom trabalho, abrçs
CurtirCurtir
Oi, Victor
Obrigado! Fico feliz que tenha ajudado!
[]s
CurtirCurtir
Strategy foi o primeiro padrão de projeto que conheci. Precisei dele, e que bom que achei seu site. Obrigado por compartilhar, Robson!
CurtirCurtir
Valeu, Johnatan!
CurtirCurtir