Design Patterns: O que são, importância, categorias, padrões populares e quando usar. (Parte 1)

·10 min de leitura

O que são Design Patterns?

Vamos deixar os termos claros. Quando falamos de design patterns, ou "padrões de projeto", não está se referindo aos "padrões" como os melhores, ou mais definitivos, mas sim a soluções para problemas comuns em design de software.

O termo "padrão" pode ser traduzido para o inglês de três maneiras diferentes: "standard", "default" ou "pattern". Muitos entendem "Padrões" no sentido de "standard", ou seja, um padrão oficial, uma definição definitiva sobre algo. Na verdade, estamos falando de "patterns": algo que se repete com frequência, seja bom ou ruim, certo ou errado, e que está longe de ser uma definição definitiva.

Nunca trate um design pattern como uma regra. É apenas uma alternativa, uma sugestão, para resolver um problema. E, acredite ou não, quase todo problema de design ou de software que você enfrenta já foi estudado e documentado por alguém.

O livro mais famoso, e precursor, sobre o assunto é "Design Patterns: Elements of Reusable Object-Oriented Software", de Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, também conhecidos como "Gang of Four" (GoF), publicado em 1994. Foi o principal responsável por disseminar o termo "design patterns" e, ao mesmo tempo, por alimentar essa má interpretação do que são "patterns".

O livro "Design Patterns", como os demais sobre o tema, é apenas um CATÁLOGO de patterns, e não um manual de como fazer design. São patterns que os autores viram em diversos projetos e aos quais simplesmente deram nome. Só isso, nada mais, nada menos.

Sua importância

Talvez isso tenha soado um pouco desnecessário, mas não é. Só quis frear as expectativas no início: daqui para a frente você vai ver como faz sentido aprender sobre patterns e pode até achar que são a solução para todos os seus problemas. Infelizmente, isso não existe em desenvolvimento de software.

De uma forma mais otimista, design patterns são estratégias de desenvolvimento que transformam desafios complexos em soluções elegantes. E sim, você deve usá-los.

É raro você estar resolvendo algo que ninguém tenha enfrentado antes. Ter à mão soluções já conhecidas para problemas comuns e recorrentes é um cenário ideal para recorrer a design patterns.

Na prática, essa comunicação padronizada é o que mais ajuda. Quando o "Pattern" é conhecido por todos, a comunicação e entendimento do código fica muito mais fácil. Não importa se é estagiário, júnior, pleno, sênior, staff engineer, etc. Todos vão entender o que está acontecendo ali.

Design de Software e Design Patterns

Antes de falarmos sobre os padrões em si, vamos deixar todo mundo na mesma página definindo o que é esse tal de design.

Arquitetura de software versus design de software é um assunto polêmico. Não vou me aprofundar neste artigo. Simplificando muito, design de software é como o software é estruturado ao nível do código. Arquitetura de software trata de componentes de alto nível, como linguagens, bancos de dados, servidores e como eles se relacionam. Claro, isso é uma simplificação grande; por enquanto, basta lembrar que design de software é, em essência, sobre código.

Dito isso, temos alguns princípios de design de software que são de mais alto nível, coisas como: SOLID, Ports e Adapters, Clean Architecture, etc. Esses princípios são mais abrangentes, são ideias, conceitos gerais e que podem ser aplicados a qualquer projeto.

Design patterns são mais específicos: tratam de implementação concreta, como criar um objeto, como organizar classes, como estruturar um trecho de código, e por aí vai. Alguns até ajudam na definição de arquitetura, mas a maioria gira em torno de design de software.

Categorias de Design Patterns

Se for para você aprender algo nesse artigo, que seja o seguinte: Todo Pattern existe para um propósito. Entenda o propósito antes da implementação.

Vou repetir, a intenção é mais importante que a implementação. Conforme você for estudando, você perceberá que muitos patterns são parecidos, e a diferença está na intenção. A intenção é o que vai fazer você escolher um pattern ou outro. E de forma secundária, a implementação.

A intenção de um pattern começa pela "categoria", ou melhor, pelo tipo de problema que ele endereça.

A primeira categoria é a mais simples, e a mais fácil de entender. É a categoria Padrões Criacionais. Padrões que ajudam na criação de objetos de forma flexível e reutilizável.

O segundo tipo é Padrões Estruturais. Padrões que ajudam na organização de classes e objetos para formar sistemas mais modulares.

E por último temos os Padrões Comportamentais. Padrões que ajudam na definição da interação entre objetos e a distribuição de responsabilidades entre eles.

Essas categorias foram definidas no livro da Gang of Four (GoF) citado anteriormente. Acaba sendo a base para a maioria dos livros e artigos sobre o assunto. Tem outras categorias, mas essas são as mais conhecidas e utilizadas. Na prática, não importa tanto a categoria, mas sim o objetivo/intenção.

De novo, foque sempre na intenção.

Exemplo de Intenções diferentes

Exemplificar é a melhor forma de entender. Vou citar alguns padrões que são muito utilizados, porém com intenções completamente diferentes. Não se preocupe em decorar os nomes ou funções. O foco aqui é entender a diferença na intenção de cada um deles.

Um bem conhecido e muito polêmico é o Singleton. Uma hora ele é amado, outra hora ele é odiado. Outro momento vou falar só sobre ele. Mas por enquanto, o que você precisa saber é que ele é um padrão criacional. A intenção é garantir que uma classe tenha apenas uma instância na aplicação, útil quando você trabalha com recursos compartilhados. A ideia é: ao "criar" o objeto de novo, não surge uma nova instância; reutiliza-se a que já existe. Isso ajuda a evitar inconsistências no sistema ou a economizar recursos. Por exemplo, em um servidor de cache: criar uma instância nova a cada uso tende a gerar inconsistência de dados. O mesmo raciocínio vale para conexões com banco de dados: em muitos cenários você prefere reutilizar a mesma conexão em vez de abrir uma nova a cada consulta. Há quem critique o Singleton; isso fica para outro momento. Por ora, basta saber que é um padrão criacional cuja intenção é garantir uma única instância da classe na aplicação.

Outro padrão é o Adapter. O Adapter é um padrão estrutural. A intenção dele é adaptar. Serve para permitir que duas interfaces incompatíveis possam trabalhar juntas. É muito útil, por exemplo, quando você precisa integrar sistemas legados com sistemas novos ou quando uma biblioteca de terceiros não segue o formato que sua aplicação espera. Um exemplo é quando você precisa integrar com diversas APIs de pagamentos. Cada empresa tem propriedades, regras e retornos diferentes. O seu sistema precisa se adaptar a cada uma delas, e ao mesmo tempo manter o código limpo e organizado.

Outro padrão é o Strategy. O Strategy é um padrão comportamental. A intenção dele é definir uma família de algoritmos, colocar cada um deles em uma classe separada e tornar seus objetos intercambiáveis. O exemplo clássico: imagine que você tem um sistema de pagamento que pode usar diferentes métodos: cartão de crédito, boleto, pix, etc. Com o padrão Strategy, você cria uma interface de pagamento e implementa o mesmo método em classes separadas. Ou seja, 3 classes diferentes, todas com um método do mesmo nome, mas com implementações diferentes. Dessa maneira, a classe responsável pelo pagamento pode ser escolhida em tempo de execução. Isso é útil quando você tem várias maneiras de fazer a mesma coisa, mas quer manter o código limpo e organizado. De novo, a implementação não é relevante agora. Entenda a intenção dele.

Nesses 3 exemplos, é muito fácil perceber a diferença entre eles, pois são de categorias diferentes. Mas quando lidamos com padrões da mesma categoria, a diferença é mais sutil. Por exemplo, Adapter e Facade: Ambos envolvem "encapsular" código. Ambos fornecem uma interface para outras classes, mas o Adapter traduz interfaces incompatíveis, como o exemplo das APIs de pagamentos. Já o Facade simplifica uma interface complexa, tornando-a mais fácil de usar e entender, escondendo a complexidade interna.

Se você não entendeu a diferença, não se preocupe. Isso é normal. Se você estudar da maneira correta, você vai entender. E de novo, estudar corretamente é entender a intenção.

Quando usar Design Patterns?

Sendo direto, não tem resposta para essa pergunta. Mas vou levantar algumas considerações que devem ser levadas em conta na hora da escolha.

Foque no seguinte: um Pattern foi feito para resolver um problema. Se você tem um problema, e esse problema parece ser tão comum, é provável que já exista um padrão que pode resolver.

O primeiro passo não é pensar no Pattern, e sim no problema. Com base no problema, você vai verificar os possíveis padrões. Então, se o código está ficando cada vez mais acoplado, se há problemas de modularidade ou de extensibilidade, se responsabilidades únicas viram dor de cabeça ou se qualquer mudança em um trecho quebra outro, é provável que existam padrões que possam ajudar.

E óbvio, não precisa decorar a implementação de todos os patterns. Você nem vai usar todos eles. Você precisa conhecer os patterns e seus objetivos, mas não precisa decorar. O que você precisa saber é que provavelmente existe um padrão para resolver o seu problema. E a partir disso, você vai pesquisar e estudar o padrão que pode te ajudar.

Outro ponto crucial é a frequência do problema no sistema. Se só um pedaço do sistema apresenta aquele problema, talvez não valha implementar um padrão: mesmo quando "caberia" um pattern ali, o custo (interfaces, classes a mais, indireções) pode ser overhead desnecessário para um caso isolado.

Se o seu código não tem reuso dentro do sistema, pense se vale a pena implementar um padrão ou se é melhor manter a simplicidade.

Se não tem muita manutenção nesse código, talvez não compense investir tempo em um padrão.

Leve todos esses pontos em consideração. E não tenha medo de não usar um padrão. Não é porque existe um padrão que você tem que usar. O padrão é uma alternativa e não uma regra.

Evite a patternite

Patternite é um termo informal para o excesso de patterns: aplicar padrões o tempo todo, sobretudo onde não há necessidade.

A simplicidade é a arte mais elegante de todas. Use os patterns quando necessário. Esse necessário é difícil de definir, eu sei. E parece que quanto menos experiente você é, mais você tende a usar patterns.

Antes de implementar, busque conversar com alguém que entenda do padrão e verifique se é necessário para aquele caso.

Mas é quase inevitável, todo mundo passa por essa fase. Com o tempo você pega esse feeling. Você vai perceber quando é necessário e quando não é. E isso só vem com tempo de prática.

Conclusão

No final do dia, nosso objetivo é deixar o código mais legível, consequentemente mais fácil de manter e escalar. Se algum pattern te ajuda a fazer isso, use-o. Se não, não use. O difícil é tomar essa decisão.

Com o tempo, você vai perceber que o bom uso dos patterns está em saber combiná-los de forma estratégica.

Referências

Esses livros são dos tipos que não precisam ser lidos do começo ao fim. Você pode ler um capítulo ou outro e escolher o que mais se aplica para o seu momento.

  • Livro: Design Patterns: Elements of Reusable Object-Oriented Software (Padrões de Projeto Soluções Reutilizáveis de Software Orientados a Objetos).

  • Livro: Patterns of Enterprise Application Architecture. (Padrões de Arquitetura de Aplicações Corporativas).

  • Site: https://refactoring.guru/design-patterns