Presenters e Decorators em Ruby

Tutoriais - 19/Set/2019 - por Campus Code

Presenters e Decorators em Ruby

SimpleDelegator na construção de Presenters e Decorators

Decorators e Presenters são padrões de programação orientada a objetos muito comuns no desenvolvimento de aplicações Ruby on Rails, utilizados para expandir as funcionalidade de classes sem que elas se tornem excessivamente grandes. Se analisarmos histórica e hierarquicamente, Presenters podem ser vistos como um subtipo de Decorator e sua diferenciação ocorre num nível mais interpretativo conforme os objetivos para os quais são utilizados. Decorators seriam estruturas de uso mais geral para adicionar responsabilidades a uma classe, enquanto Presenters estariam mais relacionados à apresentação de informações, deixando a lógica de negócio para os models e Decorators. No entanto, não existe uma regra rígida e é possível encontrar ambos sendo usados de forma variada.

Aqui vamos descrever um exemplo construindo um Presenter, mas os mesmos conceitos poderiam ser aplicados a um Decorator.

Para começar, considere uma classe Rental, que representa a locação de um carro, por exemplo. Ela possui um cliente (customer), um preço (price) e um status (status):

class Rental
 attr_accessor :status, :customer, :price

 def initialize(status, customer, price)
   @status = status
   @customer = customer
   @price = price
 end
end

Se acharmos necessário, poderíamos, por exemplo, sobrescrever o método status ou adicionar outros métodos para incluir novas funcionalidades à classe, como retornar uma string que descreve todos os atributos da instância. No entanto, rapidamente a classe Rental poderia ficar inchada e cheia de códigos relacionados à sua apresentação e não ao Model em si.

Vamos ver como isso poderia ser solucionado utilizando a estrutura do Presenter.

class RentalPresenter
 attr_reader :rental

 def initialize(rental)
   @rental = rental
 end

  def status
    "A locação está com status: #{@rental.status}"
  end

  def display
    "O cliente #{@rental.customer}, tem uma locação no valor de #{@rental.price}."
  end
end

A classe RentalPresenter recebe um objeto de Rental na sua inicialização e possui dois métodos: display e status. Dessa maneira, além do método status agora retornar uma frase mais completa, podemos utilizar o método display para apresentar o nome do cliente e o preço da locação, sem a necessidade de construir essas strings no HTML.

rental = Rental.new("Ativa", "Luiz", 45)
rental_presenter = RentalPresenter.new(rental)

puts rental_presenter.status
# => A locação está com status: Ativa 

puts rental_presenter.present
# => O cliente Luiz, tem uma locação no valor de 45.

No entanto, esperamos que o Presenter responda também aos métodos do objeto recebido. Nesse caso, RentalPresenter também deveria possuir os métodos customer e price. Existem algumas maneiras de resolver esse problema, mas aqui vamos usar o SimpleDelegator.

Ele é uma implementação da classe Delegator que fornece mecanismos que facilitam delegar métodos para o objeto passado no construtor da classe. Ajustando o código para que RentalPresenter herde de SimpleDelegator, ele ficaria assim:

class RentalPresenter < SimpleDelegator
 def status
   "A locação está com status: #{super}"
 end

 def display
   "O cliente #{customer}, tem uma locação no valor de #{price}."
 end
end

Muito mais simples, não é? Podemos esquecer o construtor. O super chamado implicitamente refere-se ao objeto passado na inicialização, assim, o RentalPresenter ganha os métodos customer, price e status. Dessa maneira, podemos chamar todos os métodos da classe Rental para RentalPresenter, além das classes expandidas.

rental = Rental.new("Ativa", "Luiz", 45)
rental_presenter = RentalPresenter.new(rental)

puts rental_presenter.customer
# => Luiz

puts rental_presenter.price
# => 45

puts rental_presenter.status
# => A locação está com status: Ativa

puts rental_presenter.display
# => O cliente Luiz, tem uma locação no valor de 45.

Considerando que estamos dentro de uma aplicação Ruby on Rails, no Controller de Rental o RentalPresenter poderia ser utilizado da seguinte maneira:

def show
  rental = Rental.find(params[:id])
  @rental = RentalPresenter.new(rental)
end

E na View todos os métodos de RentalPresenter ficam disponíveis para uso.

Em resumo, Presenters e Decorators são utilizados para ampliar as responsabilidades de classe desacoplando funcionalidades sem inchar os Models.

Os mesmos resultados poderiam ser obtidos usando herança e módulos, mas Presenters/Decorators possuem algumas vantagens, como:

  • Na herança, alterações na super classe implicam em alterações nas classes filhas.
  • Você pode, por exemplo, adicionar mais de um Decorator/Presenter se desejar e isso confere enorme flexibilidade ao código.
  • Heranças são estáticas, ou seja, as alterações acontecem na classe inteira, enquanto o Decorator/Presenter pode ser aplicado somente quando for necessário.

Vale ressaltar que esses padrões são aplicações do princípio Open/Closed, um dos 5 princípios fundamentais que ajudam devs a manter um código limpo, também conhecidos pelo acrônimo SOLID. Pelo princípio Open/Closed, uma classe deve ser aberta (open, em inglês) para extensão e fechada (closed, em inglês) para modificação. É fácil perceber como a aplicação do padrão Decorator/Presenter permite a extensão de classes. Justamente pela sua praticidade e flexibilidade, é igualmente fácil cair na tentação de abusar desses padrões, colocando lógicas que não fazem parte do escopo desses padrões. Naturalmente, é importante que esses padrões também atendam aos 5 princípios, sendo recomendado algum planejamento no design da sua aplicação.

Referências