Aplicando a Lei de Demeter na Programação Orientada a Objetos com Rails

Aplicando a Lei de Demeter na Programação Orientada a Objetos com Rails
Photo by Mohammad Rahmani / Unsplash

Na programação orientada a objetos (OOP), aderir a princípios que promovem baixo acoplamento e encapsulamento é essencial para construir sistemas escaláveis e de fácil manutenção. Um desses princípios é a Lei de Demeter, também conhecida como o Princípio do Menor Conhecimento. Esse princípio ajuda a evitar que objetos saibam demais sobre o funcionamento interno de outros objetos, um problema comum na OOP.

Neste post, vamos explorar a Lei de Demeter e demonstrar como aplicá-la em uma aplicação Ruby on Rails. Ao final, você entenderá como esse princípio pode simplificar seu código e tornar sua aplicação mais fácil de manter.

O que é a Lei de Demeter na Programação Orientada a Objetos?

Na programação orientada a objetos, a Lei de Demeter (LoD) pode ser resumida com a seguinte regra: "Um objeto deve se comunicar apenas com seus amigos imediatos e não com estranhos."

Para ser mais claro, um objeto deve invocar métodos apenas em:

  • Ele mesmo.
  • Suas dependências diretas (campos ou variáveis de instância).
  • Objetos passados como argumentos.
  • Objetos que ele cria.

Essa regra reduz as dependências entre objetos, tornando o código menos frágil e mais fácil de manter. Quando um objeto "atravessa" outro para acessar o método de um terceiro objeto (ex.: pedido.cliente.endereco.cidade), cria-se um acoplamento forte. Esse tipo de código é mais difícil de testar, estender e depurar.

O Problema: Quebrando a Lei de Demeter

Vamos analisar um exemplo comum em uma aplicação Rails onde a Lei de Demeter é violada. Suponha que temos três modelos: Pedido, Cliente e Endereco. Um Pedido pertence a um Cliente, e um Cliente possui um Endereço. No código abaixo, o Pedido calcula um preço de envio com desconto baseado no endereço do cliente.

class Pedido
  def preco_envio_com_desconto(codigo_desconto)
    cupom = Cupom.new(codigo_desconto)
    cupom.desconto(cliente.endereco.preco_envio)
  end
end

Essa implementação viola a Lei de Demeter. O objeto Pedido está acessando Cliente para chegar a Endereco e buscar o preco_envio. Isso leva a uma situação em que Pedido sabe demais sobre a estrutura interna de Cliente e Endereco, resultando em um forte acoplamento entre esses objetos.

Refatoração: Aplicando a Lei de Demeter

Para corrigir isso, podemos mover a responsabilidade de calcular o preço de envio com desconto para o modelo Cliente. Dessa forma, Pedido interage apenas com seu colaborador direto, Cliente, enquanto Cliente gerencia sua relação com Endereco.

Aqui está o código refatorado:

class Pedido
  def preco_envio_com_desconto(codigo_desconto)
    cliente.preco_envio_com_desconto(codigo_desconto)
  end
end

class Cliente
  def preco_envio_com_desconto(codigo_desconto)
    cupom = Cupom.new(codigo_desconto)
    cupom.desconto(endereco.preco_envio)
  end
end

Agora, a classe Pedido não acessa diretamente Endereco. Em vez disso, delega o cálculo ao objeto Cliente, o que reduz o acoplamento e torna o sistema mais fácil de manter.

Usando delegate para um Código Ainda Mais Limpo

Podemos levar essa refatoração um passo adiante usando o método delegate do Rails. O método delegate permite encaminhar chamadas de métodos automaticamente de um objeto para outro, tornando o código mais limpo e fácil de ler.

Veja como podemos refatorar o exemplo anterior usando delegate:

class Pedido
  belongs_to :cliente

  # Delegar preco_envio_com_desconto ao cliente
  delegate :preco_envio_com_desconto, to: :cliente
end

class Cliente
  has_one :endereco

  def preco_envio_com_desconto(codigo_desconto)
    cupom = Cupom.new(codigo_desconto)
    cupom.desconto(endereco.preco_envio)
  end
end

Como Isso Funciona?

  • Em Pedido: A linha delegate :preco_envio_com_desconto, to: :cliente permite que Pedido delegue a chamada para Cliente sem precisar definir o método manualmente.
  • Em Cliente: O método preco_envio_com_desconto gerencia a interação com Endereco e aplica a lógica de desconto.

Essa refatoração torna o código mais limpo, ao mesmo tempo em que respeita a Lei de Demeter.

Por Que Seguir a Lei de Demeter?

  1. Baixo Acoplamento
    Seguindo a Lei de Demeter, os objetos não precisam conhecer os detalhes internos de outros objetos. Isso reduz a probabilidade de que uma mudança em uma parte do sistema quebre outra parte. Por exemplo, se alterarmos a estrutura de Endereco, Pedido não precisará ser modificado.
  2. Encapsulamento
    Cada classe é responsável por sua própria lógica. No nosso exemplo, a classe Cliente é responsável por gerenciar o endereço e o preço de envio, enquanto Pedido foca nos detalhes do pedido. Essa clara separação de responsabilidades torna o código mais fácil de entender e manter.
  3. Melhor Manutenibilidade
    Menos conhecimento sobre as estruturas de outros objetos significa menos dependências. Isso torna o código mais flexível e mais fácil de refatorar no futuro. Se a lógica de negócios em torno do envio mudar, você só precisará atualizar o modelo Cliente, sem impactar Pedido.

Conclusão

A Lei de Demeter é um princípio chave na programação orientada a objetos que ajuda a reduzir o acoplamento e a promover uma melhor organização do código. Em aplicações Rails, aderir a esse princípio resulta em sistemas mais escaláveis e fáceis de manter. Refatorar seu código para delegar responsabilidades e limitar o conhecimento de um objeto sobre os outros cria uma arquitetura de aplicação mais limpa e robusta.

No nosso exemplo com Pedido, Cliente e Endereco, vimos como é fácil quebrar a Lei de Demeter e como o uso do método delegate do Rails pode ajudar a corrigir isso. À medida que sua aplicação Rails cresce, aplicar a Lei de Demeter garantirá que sua base de código permaneça limpa, fácil de manter e extensível.

Principais Conclusões:

  • A Lei de Demeter incentiva que objetos se comuniquem apenas com seus colaboradores imediatos, reduzindo as dependências.
  • Violar essa lei resulta em código fortemente acoplado, que é mais difícil de manter.
  • Usar o método delegate do Rails facilita a aplicação desse princípio, resultando em um código mais limpo e modular.