Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development

Refatorando Código Legado: Parte 8 - Invertendo Dependências para uma Arquitetura Limpa

by
Read Time:12 minsLanguages:
This post is part of a series called Refactoring Legacy Code.
Refactoring Legacy Code: Part 7 - Identifying the Presentation Layer
Refactoring Legacy Code: Part 9 - Analyzing Concerns

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Código velho. Código feio. Código complicado. Código macarrônico. Código sem sentido. Em outras palavras, Código Legado. Esta é uma série que ajudará você a trabalhar e lidar com esse tipo de código.

É hora de falar sobre arquitetura e como organizaremos nossas camadas de código. É hora de tentarmos mapear nossa aplicação em relação ao projeto arquitetural teórico.

Arquitetura Limpa

Isso é algo que temos visto por nossos artigos e tutoriais. Arquitetura limpa.

A alto nível, parece-se com o esquema acima e, tenho certeza, ele já deve ser familiar a você. É uma solução arquitetural proposta por Robert C. Martin.

No centro de nossa arquitetura está a lógica de negócios. Essas são as classes que representam os processos de negócios que nossa aplicação tentam resolver. Há entidades e interações representado o domínio de nosso problema.

Além disso, há vários outros tipos de módulos ou classes ao redor da nossa lógica de negócio. Elas podem ser vistas como módulos auxiliares. Eles servem para várias coisas e a maioria deles é indispensável. Eles proveem conexão entre o usuário e nossa aplicação, através de um mecanismo de entrega. No nosso caso, é a interface de linha de comando. Há outro conjunto de classes auxiliares que conectam nossa lógica de negócios à nossa camada de persistência e à todos os dados daquela camada, mas não temos essa camada em nossa aplicação. Além disso, há classes auxiliares de fábricas e construtores responsáveis pela criação de novos objetos de nossa lógica de negócios. Por fim, tem as classes que representam a porta de entrada do nosso sistema. No nosso caso, a classe GameRunner pode ser considerada desse tipo, ou mesmo todos nossos testes podem ser considerados portas de entrada a sua própria maneira.

O que é mais importante atentar no diagrama é a direção das dependências. Todas as classes auxiliares dependem da lógica de negócios. A lógica de negócios não dependem de qualquer outra coisa. Se todos os objetos da nossa lógica de negócios pudessem ter todos os dados com eles, magicamente, e pudéssemos ver o que acontece dentro do computador, veríamos que a lógica de negócio funcionaria sem problemas. Ela deve ser capaz de funcionar sem uma interface de usuário ou sem uma camada de negócios. A lógica de negócios deve existir isolada, em uma bolha em um universo de lógica.

O Princípio da Inversão de Dependência

A. Módulos de nível mais alto não deveriam depender de módulos de níveis mais baixos. Ambos deveriam depender de abstrações; B. Abstrações não deveriam depender de detalhes. Detalhes que devem depender das abstrações.

E é isso, o último dos princípios SOLID e aqueles com o maior efeito sobre seu código. É tanto simples de entender quanto simples de implementar.

De forma simples, é dito que algo concreto sempre deve depender de algo abstrato. Sua base de dados é bem concreta, então ela deve depender de algo mais abstrato. Sua Interface de Usuário é bem concreta, então ela deveria depender de algo mais abstrato. Suas fábricas são bem concretas, também. Logo, devem depender de algo abstrato. Mas, e a lógica do seu negócio? Dentro da lógico do seu negócio você deve continuar aplicando essas ideias, de modo que as classes mais próximas das fronteiras dependam de classes mais abstratas, presentes no coração da sua lógica de negócio.

Uma lógica de negócios pura representam o processo e comportamento de um domínio ou modelo de negócios definidos, de forma abstrata. Tal lógica de negócios não contem especificidade (coisas concretas), como valores, dinheiro, nomes de contas, senhas o tamanho de um botão ou o número de campos em um formulário. A lógica de negócios não deveria se preocupar com coisas concretas. Ela só deveria estar preocupada com os processos do seu negócio.

O Truque Técnico

Então, o Princípio da Inversão de Dependência (DIP) diz que deveríamos inverter nossas dependências sempre que há algum código que depende de algo concreto. Neste momento, nossa estrutura de dependências parece mais ou menos com isso.

A classe GameRunner, usando as funções do arquivo RunnerFunctions.php cria um objeto do tipo Game e o usa. Por outro lado, nossa classe Game, representando nossa lógica de negócios, criar e usa um objeto Display.

Logo, o executador depende de nossa lógica de negócios. E isso está correto. Contudo, nossa classe Game depende da classe Display, o que não é bom. Nossa lógica de negócios nunca deve depende de nossa camada de apresentação.

O truque técnico mais simples que podemos usar é lançar mão dos construtos abstratos de nossa linguagem de programação. Uma classe tradicional é bem mais concreta que uma classe abstrata, que, por sua vez, é mais concreta que uma interface.

Uma Classe Abstrata é um tipo especial de classe que não pode ser inicializada. Ela apenas contém definições e implementações parciais. Uma classe abstrata de base, geralmente, tem diversas classes filhas. Essas classes filhas herdam a funcionalidade parcial pré-existente, adicionam seu próprio comportamento e devem implementar todos os métodos não implementados na classe pai abstrata.

Uma Interface é um tipo especial de classe que permite apenas a definição de métodos e variáveis. É o construto mais abstrato na programação orientada a objetos. Qualquer classe que a implemente deve implementar todos os métodos da interface. Uma classe concreta pode implementar diversas interfaces.

Exceto pela família do C de linguagens orientadas a objetos, outras linguagens como o Java ou PHP não permitem herança múltipla. Assim, uma classe concreta pode estender apenas uma única classe abstrata, mas pode implementar diversas interfaces, inclusive os dois ao mesmo tempo, se preciso. Ou, de outra maneira, uma única classe abstrata pode ter diversas implementações, enquanto diversas interfaces podem ter diversas implementações.

Para uma explicação mais completa sobre o princípio da inversão de dependência, por favor, leia o tutorial dedicado a esse princípio SOLID.

Invertendo a Dependência Usando uma Interface

O PHP dá suporte completo a interfaces. Então, começando com a classe Display como nosso modelo, poderíamos definir uma interface com os métodos públicos que todas as classes responsáveis por apresentar dados precisariam implementar.

Dando uma olhada na lista de métodos da classe Display, existem 12 métodos públicos, incluindo o construtor. Essa é uma interface bem grande. Deveríamos manter esse número ao mínimo possível, expondo as interfaces de acordo com as necessidades dos clientes. O Princípio da Segregação de Interfaces nos fornece algumas boas ideias quanto a isso. Talvez lidaremos com esse problema em um tutorial futuro.

Agora, queremos alcançar uma arquitetura semelhante a que temos logo abaixo:

Dessa forma, ao invés de termos uma classe Game dependendo de uma classe concreta Display, ambas dependeriam de uma interface bem abstrata. A classe Game usará a interface, enquanto a classe Display a implementará.

Nomeando as Interfaces

Phil Karlton uma vez disse: "Só existem duas coisas complicadas na Ciência da Computação: invalidação de cache e nomeação de coisas."

Enquanto não nos preocupamos com caches, precisamos nomear nossas classes, variáveis e métodos. Nomear interfaces pode ser bem desafiador.

Antigamente, no tempo da Notação Húngara, nós teríamos feito dessa forma:

Nesse diagrama, usamos os nomes verdadeiros das classes/arquivos e usamos letras maiúsculas. A interface é chamada de "IDisplay" com "I" maiúsculos na frente de "Display". Para falar a verdade, existem linguagens de programação que exigiam esse tipo de nomeação para suas interfaces. Tenho certeza que alguns leitores ainda as usam e estão com um sorriso no rosto, neste momento.

O problema com esse esquema de nomeação é dúvida desnecessária. As interfaces pertencem a seus clientes. Nossa interface pertence à classe Game. Assim, a classe Game deve saber se usa uma interface ou um objeto real. A classe Game não precisa preocupar-se sobre qual implementação ela vai receber. Do ponto de vista da classe Game, é como se estivesse usando uma classe "Display", e mais nada.

Isso resolver o problema de nomeação entre Game e Display. Usar o sufixo "Impl" para a implementação é relativamente melhor. Ele ajuda a eliminar a dúvida da classe Game.

Além disso, é muito mais efetivo para nós. Pense na classe Game da forma que está agora. Ela usa um objeto Display e sabe como usá-lo. Se nomearmos nossa interface como "Display", reduziremos o número de mudanças necessárias na classe Game.

Ainda assim, essa nomeação é só um pouco melhor que a que estávamos usando, apenas permitindo uma implementação da classe Display e o nome da implementação não nos dirá com que tipo de apresentação estamos tratando.

Agora, sim, está consideravelmente melhor. Nossa implementação foi nomeada de "CLIDisplay", já que retorna para a linha de comando. Se quiséssemos um retorno em forma de HTML ou para uma interface de um aplicativo desktop, poderíamos adicioná-los todos à nossa arquitetura.

Mostre-me o Código

Como temos dois tipos de testes, o lento resultado esperado e os rápidos, queremos depender dos testes unitários o máximo possível, o mínimo no resultado esperado. Então, marquemos nosso os testes do resultado esperado para serem pulados e dependamos apenas dos nossos testes unitários. Eles estão verdes e queremos realizar mudanças que os mantenham verdes. Mas, como podemos fazer isso, sem realizar todas as mudanças propostas acima?

Existe algum teste que nos permitiria ir mais devagar?

A Simulação Salva o Dia

Sim, existe! Há um conceito chamado de "simulação", nos testes.

A Wikipedia define a simulação assim, "Em programação orientada a objetos, objetos simulados são objetos que imitam o comportamento de objetos reais de forma controlada."

Um objeto desses viria bem a calhar. Na verdade, não precisamos de algo tão complexo a ponto de simular todo o comportamento. Só precisamos de um objeto falso e simplório que podemos enviar para o objeto Game ao invés da lógica de apresentação verdadeira.

Criando a Interface

Criemos uma interface chamada Display com todos os métodos públicos da nossa classe concreta atual.

Como pode observar, a antiga classe Display.php foi renomeada para DisplayOld.php. Esse é apenas um passo temporário, que nos permitirá removê-lo do nosso caminho para concentrarmo-nos na interface.

Isso é tudo o que é preciso para a criação de uma interface. Veja que ela é definida como "interface" não como "class". Adicionemos os métodos:

Sim. Uma interface é apenas um conjunto de declarações de métodos. Imagine-os como um arquivo de cabeçalho do C. Sem implementações, apenas declarações. Não podemos ter uma implementação sequer. Se tentar implementar qualquer um dos métodos, resultará em um erro.

Mas essas definições tão abstratas nos permitirão realizar coisas maravilhosas. Nossa classe Game agora depende delas, ao invés de uma implementação concreta. No entanto, ao tentarmos executar nossos testes, eles falharão.

Isso é porque a classe Game tenta criar um novo objeto do tipo Display por conta própria, na linha 25, dentro do construtor.

Sabemos que não podemos fazer isso. Um interface ou uma classe abstrata não podem ser instanciadas. Precisamos de um objeto real.

Injeção de Dependência

Precisamos de um objeto postiço para ser usado em nossos testes. Uma classe simples, implementando todos os métodos da interface Display, mas fazendo nada. Criemos essa classe diretamente em nosso teste unitário. Se sua linguagem de programação não permite diversas classes no mesmo arquivo, sinta-se livre para criar um novo arquivo para sua classe postiça.

Tão logo você diga que sua classe implementa uma determinada interface, sua IDE automaticamente criará os métodos faltantes. Isso torna a criação de objetos desse tipo bem fácil e rápido.

Agora, usemos essa classe postiça na classe Game, inicilizando-a no construtor:

Isso faz os testes passarem, mas introduz um enorme problema: A classe Game precisa saber sobre seu teste e não queremos que isso ocorra. Um teste é outro ponto de entrada. A classe DummyDisplay é apenas outra interface de usuário. Nossa lógica de negócio, a classe Game, não deve depender da interface de usuário. Façamo-la depender apenas da interface.

Contudo, para testar a classe Game, precisamos passar a classe postiça a partir de nossos testes.

E é isso! Precisamos modificar uma única linha em nossos testes unitários. Na configuração, devemos enviar, por parâmetro, uma nova instância da classe DummyDisplay. Isso é o que chamamos de injeção de dependência. Usar interfaces e injeção de dependências ajudará, principalmente, se estiver trabalhando em equipe. Nós, na Syneto, percebemos que ao especificar um tipo de interface para uma classe e injetando-a, ajuda-nos a comunicar muito melhor as intenções do objeto cliente. Qualquer um olhando o código do cliente saberá que tipo de objeto é usado nos parâmetros. E um extra bacana é que sua IDE autocompleta os métodos para esses parâmetros, uma vez que eles pode determinar seus tipos.

Uma Implementação Real para o Resultado Esperado

O teste do resultado esperado executa nosso código como se ele fosse executado no mundo real. Para fazê-lo passar, precisamos transformar nossa antiga classe display em uma implementação real de uma interface e passá-la para nossa lógica de negócio. Eis uma forma de fazê-lo:

Renomeia-a para CLIDisplay e faça-a implementar a interface Display.

No arquivo RunnerFunctions.php, na função run(), crie um novo objeto de apresentação para linha de comando e o envie-o para a classe Game quando for criado.

Remova o comentário e execute os testes do seu resultado esperado. Eles passarão.

Pontos Finais

Efetivamente, essa solução leva a uma arquitetura tal qual o diagrama abaixo.

Agora, o executador do nosso jogo, que é a porta de entrada para o jogo, criar uma classe concreta do tipo CLIDisplay, dependendo, assim, dela. A classe CLIDisplay depende apenas da interface que divide a camada de apresentação da lógica de negócio. Nosso executador também depende diretamente da lógica de negócio. É assim que nossa aplicação se parece quando projetada com uma arquitetura limpa em mente desde o começo.

Obrigado pela leitura e não perca o próximo tutorial, onde falaremos sobre simulação e interação de classes em mais detalhes.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.