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.display
# => 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.