Go to English Blog

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.