📚 Clube do Livro #1: Padrões de projeto (design patterns)
Olá, tudo bem?
Já algum tempo atrás prometi revisar e resumir os livros que vocês escolhessem. Demorei um pouco mais do que meu planejado por conta de uma troca de emprego, mas cá estou eu cumprindo minha promessa e tentando atender às expectativas de todos.
Como falei anteriormente, minha ideia não é substituir a leitura do livro, mas te auxiliar a decidir se faz sentido comprá-lo. Logo, focarei em pontuar temas e trechos que eu considere interessantes, mas certamente não será todo o conteúdo relevante do livro. Sem mais delongas, vamos ao que interessa.
Resumo
Capítulo 1: Introdução
Temos uma apresentação sobre o papel de um projetista e a ideia do que é um padrão de projeto. E é interessante o fato exposto pelo livro de que estes não são os únicos padrões existentes, cobrem apenas uma porção bem delimitada da área de desenvolvimento, e que esta prática de catalogar padrões não somente deve como já se estende a outras áreas, como o livro "Padrões de Arquitetura de Aplicações Corporativas" (Martin Fowler). Cabe aos projetistas identificarem estes padrões e os problemas que endereçam, catalogando para que este conhecimento deixe de ser tácito, isto é, individual e inerente à experiência.
É importante perceber que a linguagem de programação escolhida pode influenciar no que faz sentido levar em consideração no conteúdo do livro. Python, por exemplo, implementa de forma nativa os padrões Iterator e Decorator (inclusive utilizando os mesmos nomes), exigindo uma abordagem completamente diferente do que é escrita no livro. Por serem nativos, muitas vezes exigem notações e regras específicas que tornam mais simples, quase trivial, a utilização destas estruturas.
Neste capítulo ocorre o detalhamento de como o livro está estruturado. A começar que não há necessidade de ler de forma linear, pois o livro não constrói qualquer tipo de narrativa para explicação dos padrões. Ele é um catálogo, que pode (ou melhor, deve) ser consultado a qualquer momento, em busca de encontrar o padrão que supra sua necessidade. Para facilitar as consultas, é impresso no verso da capa e em uma folha que antecede a contra-capa um índice, que contém o nome, página, e breve resumo de cada padrão.
A tabela 1.1 (O espaço dos padrões de projeto) mostra como os padrões se dividem em dois critérios: finalidade e escopo. A finalidade de um padrão de projeto pode ser para criação, estrutura ou comportamento (não detalharei agora pois são os temas dos capítulos 3, 4 e 5, respectivamente). O escopo especifíca se o padrão se aplica primariamente a classes ou a objetos. Quando aplicados a classe, é comum a utilização de herança, criando uma relação estática (imutável em tempo de execução). Quando aplicados a objetos, a herança é muitas vezes substituída pela composição e delegação, criando uma relação mais dinâmica que pode ser alterada em tempo de execução.
Na seção 1.5 (Organizando o catálogo) há uma imagem que pessoalmente acho muito interessante, mas em um aspecto bastante contemplativo, pois tive dificuldades em entender como aplicar o conceito durante minha leitura do livro (isso porque é a segunda vez, fora as consultas esporádicas):
Se alguém souber como tirar proveito deste esquema, por favor se pronuncie!
Na seção 1.6 (Como os padrões solucionam problemas de projeto), que cobre a maior parte do capítulo, temos uma importante recapitulação sobre os principais conceitos de POO, alguns bastante populares como herança, composição, delegação, agregação, mas também explora questões mais conceituais e bastante impactantes, como a granularidade de objetos e especificação de interfaces. Esta seção é um prato cheio a quem ainda não tem tanta experiência com POO, acredito não só são temas relevantes ao dia-a-dia de um desenvolvedor (que trabalhe com POO), mas que muitas vezes geram bastante dúvida para quem está iniciando os primeiros projetos. Há uma explicação sobre as principais propriedades entre programas de aplicação, toolkits e frameworks que achei muito interessante.
As seções 1.7 e 1.8 (Como selecionar um padrão de projeto e Como usar um padrão de projeto) são curtas porém obrigatórias para entender como utilizar o catálogo, e devem ser lidas com muita atenção.
Capítulo 2: Um estudo de caso: projetando um editor de documentos
Este é um capítulo interessante para quem quer entender um pouco mais sobre como reconhecer uma situação que um padrão de projeto é aplicável, e qual escolher. É como a "parte prática" que aplica muitos dos conceitos que vimos durante o primeiro capítulo. Mas não o considero essencial, principalmente para quem já tem experiência desenvolvendo, pois é apenas uma abordagem prática dos conceitos já exibidos. Eu sempre grifo o que considero importante durante minhas leituras, e o único trecho que grifei neste capítulo foi na seção 2.4 (Adornando a interface do usuário), quando ele explica sobre o conceito de fecho transparente. Por isso, serei sucinto ao falar sobre este capítulo.
Como o título já diz, temos aqui um estudo de caso baseado na contrução de um editor de documentos. Não se empolgue muito, não é que ao final do capitulo você terá construído um Word caseiro. Como os padrões de projetos são abstrações, não é necessário chegar a uma implementação de código completa para entender a necessidade do uso. Então é uma abordagem conceitual, muito mais baseada na modelagem e estrutura do projeto, e que se utiliza principalmente de diagramas de classe e esboços para representar o resultado desejado. Há código, mas apenas para demonstrar alguns trechos mais específicos.
Os próximos três capítulos compõem o catálogo de padrões de projeto. Cada um deles esclarece sobre as possíveis finalidades dos padrões. Não me aprofundarei em qualquer um dos padrões em si, por que já houve quem fizesse este trabalho anteriormente (na verdade quase qualquer outro material sobre o assunto), e provavelmente de forma muito mais competente que eu. Caso tenha interesse em conhecer os padrões de projeto detalhadamente, além do livro há um site que gosto muito: Refactoring Guru.
Capítulo 3: Padrões de criação
Este conjunto de padrões abstraem o processo de instanciação. Isto ajuda o sistema a ser independente de como os objetos são criados. Como está descrito no livro, "Há dois temas recorrentes nestes padrões. Primeiro, todos encapsulam conhecimento sobre quais classes concretas são usadas pelo sistema. Segundo, ocultam o modo como as instâncias destas classes são criadas e compostas". Quem já alterou a assinatura do construtor de uma classe e teve de alterar mais 20 arquivos para adequar ao novo formato pode ter uma ideia bastante palpável do tipo de benefício que estamos falando.
Um tema importante abordado na introdução deste capítulo é sobre a possibilidade de tornar o projeto mais flexível nos aspectos de que, como e quando é criado, e quem cria. O que quase certamente resulta em tornar a estrutura mais complexa (nada é de graça). Logo, deve fazer sentido esta flexibilidade quando olhamos para o futuro de nosso projeto principalmente. Senão, em pouco tempo teremos uma estrutura de manutenção mais difícil e sem real benefício.
Os padrões de criação são:
- Abstract Factory: Fornece uma interface para a criação de famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
- Builder: Separa a construção de um objeto complexo da sua representação, de modo que o mesmo processo de construção possa criar diferentes representações.
- Factory Method: Define uma intefrace para criar um objeto, mas deixa as subclasses decidirem qual a classe a ser instanciada. O Factory Method permite a uma classe postergar a instanciação às subclasses.
- Prototype: Especifica os tipos de objetos a serem criados usando uma instância prototípica e criar novos objetos copiando este protótipo.
- Singleton: Garante que uma classe tenha somente uma instância e fornece um ponto global de acesso para ela.
Capítulo 4: Padrões estruturais
Padrões estruturais permitem a criação de grandes estruturas através da relação entre as classes e objetos. Quando em classes, a herança é amplamente utilizada, seja para resultar em novas interfaces ou implementações. Quando em objetos, a composição é preferida, trazendo flexibilidade em tempo de execução, além de permitir estruturas arbitrariamente complexas em alguns casos. A ideia principal é o refinamento da estrutura na separação em compostos menores, que podem ser agrupados conforme necessário, formando a estrutura desejada através destes blocos menores, muitas vezes independentes entre si.
Os padrões estruturais são:
- Adapter: Converte a interface de uma classe em outra interface esperada pelos clientes. O Adapter permite que certas classes trabalhem em conjunto, pois de outra forma seria impossível por causa de suas interfaces incompatíveis.
- Bridge: Separa uma abstração da sua implementação, de modo que as duas possam variar independentemente.
- Composite: Compõe objetos em estrutura de árvore para representar hierarquias do tipo partes-todo. O Composite permite que os clientes tratem objetos individuais e composições de objetos de maneira uniforme.
- Decorator: Atribui responsabilidades adicionais a um objeto dinamicamente. Os decoradores fornecem uma alternativa flexível a subclasses para extensão da funcionalidade.
- Façade: Fornece um interface unificada para um conjunto de interfaces em um subsistema. O Façade define uma interface de nível mais alto que torna o subsistema mais fácil de usar.
- Flyweight: Usa compartilhamento para suportar grandes quantidades de objetos, de granularidade fina, de maneira eficiente.
- Proxy: Fornece um objeto representante, ou um marcador de outro objeto, para controlar o acesso ao mesmo.
Capítulo 5: Padrões comportamentais
Este é o maior e mais complexo conjunto de padrões, pois descrevem não apenas relações estruturais entre classes e objetos, mas também padrões de comunicação entre eles. Nas palavras do autor, "eles afastam o foco do fluxo de controle para permitir que você se concentre somente na maneira como os objetos são interconectados".
Eu acho interessante comentar aqui que Alan Key, criador da linguagem SmallTalk e quem cunhou o termo "orientação a objetos" já se pronunciou algumas vezes sobre que sua intenção principal para o conceito quando foi constituído é muito mais relacionado a "trocas de mensagens" entre os objetos do que suas estruturas, como relatado por ele aqui (reparem que é um texto de 1998). Logo faria sentido que este fosse o maior capítulo.
Quando em classes, a herança serve para distribuir as responsabilidades entre as classes. Já em objetos, a composição, muitas vezes em conjunto com a delegação, entram em cena para permitir um fluxo de controle distribuído adequadamente entre os participantes. Um conceito de grande importância citado aqui é o acoplamento fraco entre os componentes. Isto é uma característica poderosa que decorre do uso de vários dos padrões deste capítulo.
Os padrões comportamentais são:
- Chain of Responsability: Evita o acoplamento do remetente de uma solicitação ao seu destinatário, dando a mais de um objeto a chance de tratar a solicitação. Encadeia os objetos receptores e passa a solicitação ao longo da cadeia até que um objeto a trate.
- Command: Encapsula uma solictação como um objeto, desta forma permitindo que você parametrize clientes com diferentes solicitações, enfileire ou registre (log) solicitações e suporte operações que podem ser desfeitas.
- Interpreter: Dada uma linguagem, define uma representação para sua gramática juntamente com um interpretador que usa a representação para interpretar sentenças nesta linguagem.
- Iterator: Fornece uma maneira de acessar sequencialmente os elementos de um objeto agregado sem expor sua representação subjacente.
- Mediator: Define um objeto que encapsula como um conjunto de objetos interage. Promove o acoplamento fraco ao evitar que os objetos se refiram explicitamente uns aos outros, permitindo que você varie suas interações independentemente.
- Memento: Sem violar a encapsulação, captura e externaliza um estado interno de um objeto, de modo que o mesmo possa posteriormente ser restaurado para este estado.
- Observer: Define uma dependência um-para-muitos entre objetos, de modo que, quando um objeto muda de estado, todos os seus dependentes são automaticamente notificados e atualizados.
- State: Permite que um objeto altere seu comportamento quando seu estado interno muda. O objeto parecerá ter mudado sua classe.
- Strategy: Define uma familia de algoritmos, encapsula cada um deles e os faz intercambiáveis. Permite que um algoritmo varie independentemente dos clientes que o utilizam.
- Template Method: Define o esqueleto de um algoritmo em uma operação, postergando a definição de alguns passos para subclasses. Permitte que as subclasses redefinam certos passos de um algoritmo sem mudar sua estrutura.
- Visitor: Representa uma operação a ser executada sobre os elementos da estrutura de um objeto. Permite que você defina uma nova operação sem mudar as classes dos elementos sobre os quais opera.
Capítulo 6: Conclusão
Esta é uma curta finalização, com um pouco de contexto histórico sobre a produção da obra e algumas reflexões dos autores. Acredito que seja a parte de menor interesse para o leitor, sendo sincero (pelo menos foi para mim). Vale comentar sobre a primeira seção do capítulo (O que esperar do uso de padrões de projeto), onde os autores falam sobre alguns aspectos importantes que aparecem durante e após a aplicação dos padrões, principalmente o trecho sobre refatoração. É curto, mas bastante elucidativo para quem quer/precisa pensar no futuro do sistema.
Minhas impressões
Este livro não é para todo mundo
O livro é claro desde o início em enfatizar que foi construído tendo em mente o que era o "fino do fino" quando foi escrito: orientação a objetos.
Logo, se você não trabalha dentro deste paradigma, ou não pretende migrar para ele em breve, este livro provavelmente não é para você. Claro que há uma chance de tirar proveito de algo, mas acredito que boa parte do livro vai soar como inútil no seu dia-a-dia.
E a questão não é a qualidade do conteúdo, mas que boa parte das preocupações que os autores destrincham aqui podem não fazer sentido fora do contexto de orientação a objetos. Para alguém da área de embarcados por exemplo, que frequentemente recorre a linguagens e paradigmas que otimizem a execução de código focada na redução de uso de recursos, muito do que está aqui poderia ser visto, no mínimo, como disperdício.
Este é um livro pensado para quem já conhece os conceitos básicos de POO, e apesar de recapitular alguns dos conceitos sobre o assunto, é apenas o suficiente para que não haja dúvidas que o leitor e o livro estão na mesma página (ba dum tss). Ele realmente não server para alguém que está tendo seu primeiro contato com POO.
Esta colocação traz ainda uma discussão um pouco mal resolvida para mim (eu debati sozinho tentando chegar a uma conclusão), mas como já vi este assunto em tantas vagas juniores, acho que vale a pena pontuar aqui.
É um livro para juniores?
Talvez! Pelo menos para os mais corajosos. Mas acho um tanto desleal cobrar isso em uma vaga como requisito obrigatório.
Não vejo um grande impedimento para que um iniciante leia este livro, exceto de que a pessoa pode não estar familiarizada com diversos dos conceitos apresentados no livro. Tive POO durante a minha graduação pautada sobre seus 4 conceitos pilares (herança, abstração, encapsulamento e polimorfismo). Só fui conhecer formalmente os conceitos de delegação e composição um bom tempo depois (quando li pela primeira vez este livro), durante estudos fora do contexto acadêmicos. E estes são igualmente importantes se comparados com a herança, por exemplo.
Estes conceitos podem ainda gerar no iniciante certa crença em uma espécie de receita mística. Isto porque o valor destas abordagens não vem só daquilo que foi construído, mas daquilo que foi evitado. Muitas vezes você só descobre a importância de um bom design quando paga o preço que o mal design te cobra. Isto ainda levando em conta de que você é um desenvolvedor sensato o suficiente para perceber a raiz do seu problema. Acredite, nem todos são. Certamente este preço é proporcional ao tamanho do projeto (eu diria de crescimento exponencial).
É normal que iniciantes lidem com projetos mais simples, logo, nem sempre entenderão de forma clara o que ele está evitando quando utilizam estes padrões, levando as vezes a acreditar que é uma abordagem supérflua por muitas vezes requerir um trabalho razoável para ser implementado sem um benefício imediato. Mas, sinceramente, a única vantagem em experienciar um projeto mal estruturado é obtida quando se consegue corrigir suas falhas de design e aperfeiçoá-lo. E você não imagina o quão difícil é fazer isto na vida real, é como trocar as asas de um avião enquanto ele voa.
A questão é que conhecer os padrões pode auxiliar a caminhar em um ritmo mais próximo que o dos desenvolvedores mais experientes da equipe (o próprio autor cita este fato), permitindo entender com maior facilidade não só as discussões como também os códigos gerados pelos demais.
Os padrões são sugestões
Uma questão que percebo ser comum entre vários, principalmente os com menos experiências, é a falsa premissa de que os padrões sempre são o melhor caminho. A simplicidade é uma forma muito elegante de se trabalhar, e só devemos abraçar a complexidade quando é realmente necessário. Debater com pessoas mais experientes pode ajudar a mitigar este tipo de falácia, entendendo quando é necessário pensar além do momento atual e se preparar com estruturas mais complexas.
Agora, se você tem um pouco mais de experiência, provavelmente este livro irá te causar flashbacks de situações específicas que tenha enfrentado, com ou sem sucesso, e esta conexão será muito boa, pois te facilitará assimilar o que é que você está evitando, gerando um vínculo muito mais concreto com uma experiência anterior que desejasse evitar.
O importante é "discutir" com o livro. Entender suas indicações para definir se faz ou não sentido este caminho ou aquele. Até por isso todos os padrões possuem duas sessões chamadas "Aplicabilidade" e "Consequências", para que você reflita sobre quando faz sentido usar aquela abordagem, suas vantagens e desvantagens, e decida por contra própria se vale o "investimento".
Onde adquirir
Como associado da Amazon, eu recebo por compras qualificadas.
Comprando através deste link, você estará contribuindo ainda mais com meu esforço, e ficarei muito feliz de saber que este material te impactou a ponto de decidir investir neste livro.
Independente da compra, você pode contribuir comigo comentando sua opinão, acrescentando algo ou até mesmo discordando comigo em algum ponto. Eu não me aprofundei nos padrões, mas nada impede discutirmos sobre qualquer um deles nos comentários. Para caso de dúvidas sobre implementação, provavelmente conseguirei te auxiliar em Python, pois é minha linguagem de domínio, as demais não garanto nada mas podemos tentar ainda assim. Com certeza haverá alguém que te ajude aqui no TabNews.
Saúde e paz a todos e bons estudos!