Como engenheiros de software, uma das nossas principais responsabilidades é mapear o mundo real através de um software que aborde os seus problemas.
Visto que a maioria das aplicações permanecem funcionais após a sua criação, a flexibilidade é uma necessidade. Se assim não for, novos requisitos de vez em quando serão um grande desafio e uma tarefa árdua.
Os padrões de desenho, também conhecidos por design patterns, vieram para te ajudar a responder a essas necessidades.
Cada problema de design, num contexto de aplicação específico, tende a ter o seu padrão de identidade. Tal defeito num sistema pode comprometer uma grande solução, transformando-o numa solução não flexível, cara e difícil de manter. Geralmente, os novos requisitos irão comprometer outras funcionalidades.
Um padrão de desenho é caracterizado por evidenciar e trabalhar em torno de tais falhas de design. Descreve também como as entidades dentro de um sistema devem interagir e ser expostas. Podemos encará-lo como um plano, que pode ser personalizado para resolver um problema de design recorrente no teu código.
Os padrões podem por vezes ser mal compreendidos como um pedaço de código que podes facilmente copiar e colar. Porém, estes devem ser entendidos como conceitos teóricos para resolver um determinado problema.
Os padrões de desenho podem ser divididos em quatro categorias distintas:
É sempre uma questão de contrapartidas na escolha de um padrão de desenho em detrimento do outro.
Os padrões de desenho podem ser de três categorias diferentes:
Estas categorias diferem em termos de complexidade, nível de detalhe, e escalabilidade quando aplicadas a todo o sistema.
Estes padrões são tecnologicamente agnósticos, e podes aplicá-los facilmente a qualquer linguagem ou framework de programação.
Tal tipo de padrão de desenho fornece vários mecanismos de criação, aumentando a flexibilidade do código, e a sua reutilização. Sumariza o processo de instanciação, ao separar a forma como os objetos devem ser criados, compostos, e representados do código que neles se baseia.
Descreve como as entidades devem ser organizadas em estruturas maiores, mantendo-as flexíveis e eficientes.
Tais padrão de desenho identificam padrões de interação comuns e aumentam a flexibilidade na realização desta comunicação. Ao fazê-lo, permitem que essas entidades falem facilmente umas com as outras, mantendo o loose coupling entre componentes.
Depois de ter introduzido os conceitos teóricos, deixa-me agora dar-te um exemplo real da aplicação de padrão de desenho, ao falar-te de um problema que tive de enfrentar no trabalho.
Certa altura tinha um ticket onde o objetivo era o refactoring do nosso serviço de extração de ficheiros de dados. Visualmente refletia-se em ter de carregar um ficheiro, tal como um PDF ou um Excel. Depois do carregamento, precisávamos de extrair todos os dados do ficheiro e apresentá-los através de uma bonita interface ao utilizador. Todos sabíamos que um novo tipo de ficheiro viria na nossa direção num futuro sprint, reforçando a necessidade de um tal refactoring.
Diagrama mostrando a estrutura antes de aplicar qualquer solução.
Observámos ao longo do desenvolvimento que muitos algoritmos semelhantes estavam a ser criados, tornando a tarefa de novas adições mais complicada e menos flexível. Uma alteração à especificação, tal como uma alteração de uma restrição, iria necessitar de ser refeita igualmente em dois componentes diferentes. Em termos de legibilidade, se tivesse sete tipos diferentes de ficheiros a carregar, a probabilidade de faltar alguns passos cruciais poderia ser elevada.
Felizmente, os padrão de desenho descrevem vários problemas que outros engenheiros enfrentaram durante as suas carreiras. Grande parte da argumentação é feita, deixando-te a tarefa de reconhecer a questão e aplicar a solução adequada.
Enquanto investigava vários padrão de desenho, o Template Method Pattern parecia ser a solução para o tipo de desafio que estávamos a enfrentar.
O mesmo sugere a existência de um esqueleto partilhado através de outras subclasses. Dentro deste esqueleto, podemos definir várias etapas, cujo comportamento pode variar com base no contexto. Nesses casos, uma subclasse deve reescrever a etapa e introduzir as suas especificidades sem alterar a estrutura global do algoritmo.
Perfeito, era mesmo disto que precisávamos!
Seguir esta abordagem permite-nos:
Tecnicamente, no final ficava assim:
A estrutura final.
Como podes ver, há um método modelo que utilizámos como ponto de partida. Além deste método modelo, que não deve ser alterado por subclasses, temos vários passos que as subclasses podem substituir em caso de necessidade.
Para implementar isto, seguimos o seguinte projeto:
Existem muitas soluções para múltiplos problemas que se podem ter, e cada solução tem os seus prós e os seus contras. Depende sempre de ti e da tua equipa decidir a melhor abordagem para cada problema que encontras no caminho.
Identificar o problema e adaptá-lo a um determinado padrão é uma das coisas mais desafiantes que um engenheiro enfrentará durante a sua carreira. É tudo uma questão de prática e experiência.
Conheço pessoas com mais de dez anos de experiência em engenharia que ainda lutam para identificar alguns deles. Por isso não te preocupes, porque o tempo e a experiência em código vão ajudar-te com isso.