Entendendo a visibilidade de métodos do Ruby
Leia em 2 minutos
Uma dúvida muito comum de quem começa a programar em Ruby é em relação à visibilidade de métodos. Afinal de contas, qual a verdadeira diferença entre métodos públicos, privados e protegidos no Ruby?
No Ruby é possível usar três palavras-chave para definir a visibilidade dos métodos. Elas podem ser definidas por um contexto (meio que como uma annotation), como no exemplo abaixo.
class Report
def render
render_header
render_body
render_footer
end
private
def render_header
puts __method__
end
def render_body
puts __method__
end
def render_footer
puts __method__
end
end
Por padrão, todo método definido já público, ou seja, pode ser invocado como uma interface pública. Este é o caso do método Report#render
. Existe uma exceção aqui; trata-se do método BasicObject#initialize
, que é sempre privado, mesmo sem precisarmos fazer esta indicação.
Alternativamente, você pode fazer a definição da visibilidade passando uma lista de nomes.
class Report
def render
render_header
render_body
render_footer
end
def render_header
puts __method__
end
def render_body
puts __method__
end
def render_footer
puts __method__
end
private :render_header, :render_body, :render_footer
end
E à partir do Ruby 2.1 também é possível fazer a definição da visibilidade em uma única linha.
class Report
def render
render_header
render_body
render_footer
end
private def render_header
puts __method__
end
private def render_body
puts __method__
end
private def render_footer
puts __method__
end
end
Note que isso só é possível porque, à partir do Ruby 2.1, o def
retorna o nome do método adicionado. Na prática, isso seria equivalente a fazer algo como o exemplo abaixo, mas sem a necessidade de duplicar o nome do método.
class Report
def render
render_header
render_body
render_footer
end
def render_header
puts __method__
end
private :render_header
def render_body
puts __method__
end
private :render_body
def render_footer
puts __method__
end
private :render_footer
end
Qual modo você deve usar é uma escolha sua, mas acho que a definição por contexto e a chamada de private
com uma lista de nomes parecem mais legíveis.
Mas qual a diferença entre private
e protected
? A única diferença está no modo como os métodos getters podem ser invocados. Os métodos privados não podem ser chamados através de um receiver; esta restrição não se aplica aos métodos protegidos.
class Sample
def initialize
some_private_method
some_protected_method
begin
self.some_private_method
rescue
puts('Cannot invoke private methods through a receiver')
end
self.some_protected_method
end
private def some_private_method
puts __method__
end
protected def some_protected_method
puts __method__
end
end
Sample.new
#=> some_private_method
#=> some_protected_method
#=> Cannot invoke private methods through a receiver
#=> some_protected_method
Um fato curioso que vale ser mencionado; essa restrição do receiver não se aplica aos métodos setters no contexto da instância (self
).
class Sample
attr_accessor :protected_attribute, :private_attribute
protected :protected_attribute=
private :protected_attribute=
def initialize
self.protected_attribute = 'some protected value'
self.private_attribute = 'some private value'
end
end
sample = Sample.new
sample.protected_attribute
#=> some protected value
sample.private_attribute
#=> some private value
Mas lembre-se que se você tentar fazer a atribuição externamente, uma exceção será lançada.
sample = Sample.new
sample.protected_attribute = 'another protected value'
#=> private method `protected_attribute=' called for #<Sample:0x007fdbfa91aef0> (NoMethodError)
Vale lembrar que o uso de herança não afeta o modo como a definição de visibilidade funciona.
class A
attr_accessor :title, :description
protected :title, :title=
private :description, :description=
end
class B < A
def initialize
self.title = 'Some title'
self.description = 'Some description'
puts title
puts self.title
puts description
begin
puts self.description
rescue
puts 'Cannot call private methods through a receiver'
end
end
end
B.new
#=> Some title
#=> Some title
#=> Some description
#=> Cannot call private methods through a receiver
Finalizando
De um modo geral, use sempre private
. Já que a diferença é inexistente para os métodos setters, o uso de private
irá forçar com que a chamada de métodos getters seja feita sem o uso de um receiver (nesse caso o self
), algo que você já deveria estar fazendo para começo de conversa.