Por que Ruby tem métodos privados e protegidos?

141

Antes de ler este artigo , pensei que o controle de acesso no Ruby funcionasse assim:

  • public- pode ser acessado por qualquer objeto (por exemplo Obj.new.public_method)
  • protected - só pode ser acessado de dentro do próprio objeto, bem como de qualquer subclasse
  • private - igual a protegido, mas o método não existe nas subclasses

No entanto, parece que protectede privateage da mesma maneira, exceto pelo fato de que você não pode chamar privatemétodos com um receptor explícito (ou seja self.protected_method, funciona, mas self.private_methodnão).

Qual o sentido disso? Quando existe um cenário em que você não deseja que seu método seja chamado com um receptor explícito?

Kyle Slattery
fonte
3
Se todas as instâncias de Objecttivessem permissão para chamar os métodos privados de todas as outras instâncias Object, seria possível dizer coisas como 5.puts("hello world").
precisa saber é o seguinte

Respostas:

161

protected métodos podem ser chamados por qualquer instância da classe de definição ou de suas subclasses.

privateOs métodos podem ser chamados apenas de dentro do objeto de chamada. Você não pode acessar os métodos privados de outra instância diretamente.

Aqui está um exemplo prático rápido:

def compare_to(x)
 self.some_method <=> x.some_method
end

some_methodnão pode estar privateaqui. Deve ser protectedporque você precisa dele para receber receptores explícitos. Seus métodos típicos de ajudante interno geralmente podem ser, privatepois nunca precisam ser chamados assim.

É importante observar que isso é diferente da maneira como o Java ou C ++ funciona. privateno Ruby é semelhante ao protectedJava / C ++, pois as subclasses têm acesso ao método. No Ruby, não há como restringir o acesso a um método de suas subclasses, como você pode fazer privateem Java.

A visibilidade no Ruby é basicamente uma "recomendação", pois você sempre pode acessar um método usando send:

irb(main):001:0> class A
irb(main):002:1>   private
irb(main):003:1>   def not_so_private_method
irb(main):004:2>     puts "Hello World"
irb(main):005:2>   end
irb(main):006:1> end
=> nil

irb(main):007:0> foo = A.new
=> #<A:0x31688f>

irb(main):009:0> foo.send :not_so_private_method
Hello World
=> nil
dbyrne
fonte
9
Ah, ok, isso faz muito mais sentido. Meu mal-entendido veio do pensamento privatevs protectedtinha que fazer se uma subclasse poderia herdar um método, mas na verdade é sobre de onde o método pode ser chamado. Obrigado!
Kyle Slattery
3
Por outro lado, os métodos privados são ignorados por padrão pelo RDoc ao gerar documentação enquanto os protegidos não são. Você sempre pode usar o sinalizador --all para incluí-los.
jasoares
Mas se você realmente deseja que seja privado, não pode substituir send?
Cyoce 25/01
78

A diferença

  • Qualquer pessoa pode chamar seus métodos públicos.
  • Você pode chamar seus métodos protegidos ou outro membro de sua classe (ou uma classe descendente) pode chamar seus métodos protegidos de fora. Ninguém mais pode.
  • Somente você pode chamar seus métodos privados, porque eles só podem ser chamados com um receptor implícito de self. Mesmo você não pode ligar self.some_private_method; você deve ligar private_methodcom o selfimplícito.
    • O iGEL aponta: "Há uma exceção, no entanto. Se você possui um método privado age =, pode (e precisa) chamá-lo consigo mesmo para separá-lo das variáveis ​​locais".
    • Desde o Ruby 2.7, o selfreceptor pode ser explícito, self.some_private_methodé permitido. (Qualquer outro receptor explícito ainda não será permitido, mesmo que o valor do tempo de execução seja o mesmo que self.)

Em Ruby, essas distinções são apenas conselhos de um programador para outro. Métodos não públicos são uma maneira de dizer "Eu me reservo o direito de mudar isso; não dependa disso". Mas você ainda recebe as tesouras afiadas sende pode chamar o método que desejar.

Um breve tutorial

# dwarf.rb
class Dwarf
  include Comparable

  def initialize(name, age, beard_strength)
    @name           = name
    @age            = age
    @beard_strength = beard_strength
  end

  attr_reader :name, :age, :beard_strength
  public    :name
  private   :age
  protected :beard_strength

  # Comparable module will use this comparison method for >, <, ==, etc.
  def <=>(other_dwarf)
    # One dwarf is allowed to call this method on another
    beard_strength <=> other_dwarf.beard_strength
  end

  def greet
    "Lo, I am #{name}, and have mined these #{age} years.\
       My beard is #{beard_strength} strong!"
  end

  def blurt
    # Not allowed to do this: private methods can't have an explicit receiver
    "My age is #{self.age}!"
  end
end

require 'irb'; IRB.start

Então você pode executar ruby dwarf.rbe fazer o seguinte:

gloin = Dwarf.new('Gloin', 253, 7)
gimli = Dwarf.new('Gimli', 62,  9)

gloin > gimli         # false
gimli > gloin         # true

gimli.name            # 'Gimli'
gimli.age             # NoMethodError: private method `age'
                         called for #<Dwarf:0x007ff552140128>

gimli.beard_strength # NoMethodError: protected method `beard_strength'
                        called for #<Dwarf:0x007ff552140128>

gimli.greet          # "Lo, I am Gimli, and have mined these 62 years.\
                           My beard is 9 strong!"

gimli.blurt          # private method `age' called for #<Dwarf:0x007ff552140128>
Nathan Long
fonte
8
Boa explicação! Há uma exceção no entanto. Se você possui um método privado age=, pode (e precisa) chamá-lo selfpara separá-lo das variáveis ​​locais.
iGEL
Se você fez do "greet" um método protegido, por que não pode fazer um gimli.greet? Como gimli é um membro da classe Dwarf, não deveria ser capaz de chamar esse método sem assédio?
JoeyC
@ JoeyC porque, quando você faz gimli.greet, gimlinão é o chamador, mas o receptor. O chamador é o "ambiente de execução de nível superior", que na verdade é uma instância ad-hoc Object. Tente isto: #ruby -e 'p self; p self.class'
Kelvin
52

Métodos particulares em Ruby:

Se um método é privado no Ruby, ele não pode ser chamado por um receptor explícito (objeto). Só pode ser chamado implicitamente. Pode ser chamado implicitamente pela classe em que foi descrita, bem como pelas subclasses dessa classe.

Os exemplos a seguir ilustrarão melhor:

1) Uma classe Animal com método privado class_name

class Animal
  def intro_animal
    class_name
  end
  private
  def class_name
    "I am a #{self.class}"
  end
end

Nesse caso:

n = Animal.new
n.intro_animal #=>I am a Animal
n.class_name #=>error: private method `class_name' called

2) Uma subclasse de Animal chamada Amphibian:

class Amphibian < Animal
  def intro_amphibian
    class_name
  end 
end 

Nesse caso:

  n= Amphibian.new
  n.intro_amphibian #=>I am a Amphibian
  n.class_name #=>error: private method `class_name' called

Como você pode ver, os métodos privados podem ser chamados apenas implicitamente. Eles não podem ser chamados por receptores explícitos. Pelo mesmo motivo, métodos privados não podem ser chamados fora da hierarquia da classe de definição.

Métodos protegidos em Ruby:

Se um método estiver protegido no Ruby, ele poderá ser chamado implicitamente pela classe de definição e por suas subclasses. Além disso, eles também podem ser chamados por um receptor explícito, desde que o receptor seja próprio ou da mesma classe que o próprio:

1) Uma classe Animal com método protegido protect_me

class Animal
  def animal_call
    protect_me
  end
  protected
  def protect_me
    p "protect_me called from #{self.class}"
  end  
end

Nesse caso:

n= Animal.new
n.animal_call #=> protect_me called from Animal
n.protect_me #=>error: protected method `protect_me' called

2) Uma classe de mamíferos herdada da classe de animais

class Mammal < Animal
  def mammal_call
    protect_me
  end
end 

Nesse caso

n= Mammal.new
n.mammal_call #=> protect_me called from Mammal

3) Uma classe de anfíbios herdada da classe Animal (igual à classe de mamíferos)

class Amphibian < Animal
  def amphi_call
    Mammal.new.protect_me #Receiver same as self
    self.protect_me  #Receiver is self
  end   
end

Nesse caso

n= Amphibian.new
n.amphi_call #=> protect_me called from Mammal
             #=> protect_me called from Amphibian  

4) Uma classe chamada Árvore

class Tree
  def tree_call
    Mammal.new.protect_me #Receiver is not same as self
  end
end

Nesse caso:

n= Tree.new
n.tree_call #=>error: protected method `protect_me' called for #<Mammal:0x13410c0>
Aaditi Jain
fonte
7

Considere um método privado em Java. Pode ser chamado de dentro da mesma classe, é claro, mas também pode ser chamado por outra instância dessa mesma classe:

public class Foo {

   private void myPrivateMethod() {
     //stuff
   }

   private void anotherMethod() {
       myPrivateMethod(); //calls on self, no explicit receiver
       Foo foo = new Foo();
       foo.myPrivateMethod(); //this works
   }
}

Então - se o chamador é uma instância diferente da minha mesma classe - meu método privado é realmente acessível a partir de "fora", por assim dizer. Na verdade, isso parece não ser tão privado.

Em Ruby, por outro lado, um método privado realmente deve ser privado apenas para a instância atual. É isso que a remoção da opção de um receptor explícito fornece.

Por outro lado, certamente devo dizer que é bastante comum na comunidade Ruby não usar esses controles de visibilidade, já que o Ruby oferece maneiras de contorná-los de qualquer maneira. Diferentemente do mundo Java, a tendência é tornar tudo acessível e confiar em outros desenvolvedores para não estragar tudo.

Jacob Mattison
fonte
9
"é bastante comum na comunidade Ruby não usar esses controles de visibilidade" - isso pode ser verdade, mas eu diria que devemos usá-los. Como constantes, elas não são algemas, mas uma comunicação de um programador para outro: "Eu aconselho você a deixar isso em paz". Você pode depender dos meus métodos públicos; Posso alterar meus métodos particulares sem aviso, porque os considero detalhes de implementação.
Nathan Long
Em Ruby, por outro lado, um método privado realmente deve ser privado apenas para a instância atual. " Isso não é verdade. Você ainda pode sobrescrever acidentalmente métodos privados da sua classe pai (e algumas classes até listam isso como parte da API).
Franklin Yu
1
@FranklinYu Isso não tem influência no que ele escreveu; a privacidade no Ruby é sobre objetos , não classes , e é sobre chamar métodos, não defini- los. Um método privado só pode ser chamado por outro método do mesmo objeto; não tem nada a ver com a classe em que o método foi definido.
philomory 20/01
2

Parte da razão pela qual métodos privados podem ser acessados ​​por subclasses no Ruby é que a herança do Ruby com classes é uma fina camada de açúcar sobre o Módulo inclui - no Ruby, uma classe, de fato, é um tipo de módulo que fornece herança, etc.

http://ruby-doc.org/core-2.0.0/Class.html

O que isso significa é que basicamente uma subclasse "inclui" a classe pai, para que efetivamente as funções da classe pai, incluindo funções privadas , sejam definidas na subclasse também.

Em outras linguagens de programação, chamar um método envolve transformar o nome do método em uma hierarquia de classes pai e encontrar a primeira classe pai que responde ao método. Por outro lado, em Ruby, enquanto a hierarquia da classe pai ainda está lá, os métodos da classe pai são incluídos diretamente na lista de métodos da subclasse definida.

madumlao
fonte
2

Comparação de controles de acesso do Java contra o Ruby: Se o método for declarado privado em Java, ele poderá ser acessado apenas por outros métodos da mesma classe. Se um método é declarado protegido, ele pode ser acessado por outras classes existentes no mesmo pacote, bem como por subclasses da classe em um pacote diferente. Quando um método é público, é visível para todos. Em Java, o conceito de visibilidade do controle de acesso depende de onde essas classes se encontram na hierarquia de herança / pacote.

Enquanto no Ruby, a hierarquia de herança ou o pacote / módulo não se encaixa. É tudo sobre qual objeto é o receptor de um método.

Para um método privado em Ruby, nunca pode ser chamado com um receptor explícito. Podemos (apenas) chamar o método privado com um receptor implícito.

Isso também significa que podemos chamar um método privado de dentro de uma classe na qual ele é declarado, bem como de todas as subclasses dessa classe.

class Test1
  def main_method
    method_private
  end

  private
  def method_private
    puts "Inside methodPrivate for #{self.class}"
  end
end

class Test2 < Test1
  def main_method
    method_private
  end
end

Test1.new.main_method
Test2.new.main_method

Inside methodPrivate for Test1
Inside methodPrivate for Test2

class Test3 < Test1
  def main_method
    self.method_private #We were trying to call a private method with an explicit receiver and if called in the same class with self would fail.
  end
end

Test1.new.main_method
This will throw NoMethodError

Você nunca pode chamar o método privado de fora da hierarquia de classes em que foi definido.

O método protegido pode ser chamado com um receptor implícito, como privado. Além disso, o método protegido também pode ser chamado por um receptor explícito (apenas) se o receptor for "próprio" ou "um objeto da mesma classe".

 class Test1
  def main_method
    method_protected
  end

  protected
  def method_protected
    puts "InSide method_protected for #{self.class}"
  end
end

class Test2 < Test1
  def main_method
    method_protected # called by implicit receiver
  end
end

class Test3 < Test1
  def main_method
    self.method_protected # called by explicit receiver "an object of the same class"
  end
end


InSide method_protected for Test1
InSide method_protected for Test2
InSide method_protected for Test3


class Test4 < Test1
  def main_method
    Test2.new.method_protected # "Test2.new is the same type of object as self"
  end
end

Test4.new.main_method

class Test5
  def main_method
    Test2.new.method_protected
  end
end

Test5.new.main_method
This would fail as object Test5 is not subclass of Test1
Consider Public methods with maximum visibility

Resumo

Público: métodos públicos têm visibilidade máxima

Protected: O método protegido pode ser chamado com um receptor implícito, como privado. Além disso, o método protegido também pode ser chamado por um receptor explícito (apenas) se o receptor for "próprio" ou "um objeto da mesma classe".

Privado: para um método privado em Ruby, ele nunca pode ser chamado com um receptor explícito. Podemos (apenas) chamar o método privado com um receptor implícito. Isso também significa que podemos chamar um método privado de dentro de uma classe na qual ele é declarado, bem como de todas as subclasses dessa classe.

Neha Chopra
fonte
0
First Three types of access specifiers and those define thier scope.
1.Public    ->  Access anywhere out side the class.
2.Private   ->  Can not access outside the class. 
3.Protected ->  This Method not access anywhere this method define 
                scope.

But i have a solution for this problem for all method how to access explain in depth. 

class Test
attr_reader :name
def initialize(name)
  @name = name
end

def add_two(number)
  @number = number 
end

def view_address
  address("Anyaddress")
end

private 
def address(add)
   @add = add
end

protected 
def user_name(name)
  # p 'call method'
  @name = name
end
end

class Result < Test
def new_user
  user_name("test355")
end
end
  1. Lista de Objetos
  2. p test = Test.new ("teste")
  3. p test.name
  4. p test.add_two (3)
  5. Item da lista
  6. p test.view_address
  7. pr = Result.new ("")
  8. p r.new_user
hardik
fonte
Algum problema na edição do código. Segunda aula show em uma única linha post anterior. Agora eu explico como acessar todos os métodos. Primeiro, crie o objeto de classe de teste. criamos o acesso ao método view_address através do objeto principal. e também o método protegido acessa a criação da herança.
hardik 19/09/19