Command Pattern

Há algum tempo, recebi um desafio prático bem legal. Basicamente, deveria implementar um robô capaz de andar N casas para a direita, esquerda, cima e baixo em um plano (10×10) circular, ou seja, assim que o robô atingisse a casa 9, o próximo passo seria para a casa 0. Algumas regras deveriam ser seguidas, como utilizar o S.O.L.I.D.

Clique aqui para ver o enunciado e a implementação completa.

Analisando o enunciado, parece não ser algo complicado. Uma ou duas classes resolveria o nosso problema, correto? Bom, eu acho que não! Em algum momento, enquanto estava implementando, me deparei com a seguinte situação:

Um método walk() dentro do objeto Robot, que decide qual método deverá chamar de acordo com a direção escolhida pelo usuário. Mas isso é um problema? Voltando ao enunciado, uma das regras era a utilização do S.O.L.I.D. Mas afinal, o que é isso? S.O.L.I.D. é um acrônimo, em que cada letra representa um princípio que te ajuda a desenvolver códigos melhores. Como o foco deste post não são os princípios, não vou abordá-los. Clique aqui para saber mais sobre o S.O.L.I.D.

O [O] do S.O.L.I.D. representa o princípio do aberto e fechado (open/closed).

Entidades de software devem estar abertas para expansão e fechadas para modificação.

Levando isso em consideração, nosso método walk() está de acordo? Caso surja uma nova funcionalidade que permita que o robô ande em diagonal, quais seriam os impactos no código? Caso isso acontecesse, da forma como fizemos, teríamos que criar um novo método com a lógica para que o robô pudesse andar em diagonal e adicionar um novo case no switch do método walk(), para que esse novo método seja invocado quando a direção escolhida pelo usuário fosse diagonal, correto? Conseguimos adicionar a funcionalidade, mas tivemos que modificar nosso método walk(). Como podemos evitar a modificação do método walk()?

Padrão Command

A ideia do padrão é encapsular a lógica em objetos que implementam um contrato, fazendo com que o “cliente” acesse o “comando” pela interface, sem se preocupar com a complexidade da lógica de negócio. Vamos à refatoração:

Primeiro, criaremos a interface Command:

Depois, criaremos as classes concretas que implementarão essa interface:




Desta forma, podemos ordenar que o robô ande da seguinte forma:

Perceba que a interface command pode receber uma instância de todas as classes concretas que criamos. Mas como saber qual classe devemos instanciar? Para isso, criaremos uma Factory, para nos devolver a instancia correta de acordo com a direção selecionada pelo usuário:

Agora vamos corrigir nosso método walk() na classe Robot:

Agora, ao invés de um switch-case, temos a Fábrica de comandos, que recebe a direção e executa o comando correto de acordo com a escolha do usuário. Com essa refatoração, para que o robô ande na diagonal, basta criar uma classe concreta que implemente a interface Command e chame o comportamento de andar em diagonal do robô no método execute(). Também precisaremos adicioná-lo na Factory. E pronto! Seu robô tem um design que permite expansão, sem modificar a necessidade de modificar comportamentos já existentes.

Aqui segue a classe de teste para validar o design implementado:

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s