Como criar um método de classe particular?

216

Como é que essa abordagem de criação de um método de classe privada funciona:

class Person

  def self.get_name
    persons_name
  end

  class << self

    private

    def persons_name
      "Sam"
    end
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name  #=> raises "private method `persons_name' called for Person:Class (NoMethodError)"

Mas isso não:

class Person

  def self.get_name
    persons_name
  end

  private

  def self.persons_name
    "Sam"
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
99 milhas
fonte
7
Eu só vi este artigo discutindo maneiras de criar métodos de classe privado e pensei que era bom: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/...
Nathan Long

Respostas:

265

privateparece não funcionar se você estiver definindo um método em um objeto explícito (no seu caso self). Você pode usar private_class_methodpara definir métodos de classe como privados (ou como você descreveu).

class Person
  def self.get_name
    persons_name
  end

  def self.persons_name
    "Sam"
  end

  private_class_method :persons_name
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name

Como alternativa (no ruby ​​2.1+), como uma definição de método retorna um símbolo do nome do método, você também pode usá-lo da seguinte maneira:

class Person
  def self.get_name
    persons_name
  end

  private_class_method def self.persons_name
    "Sam"
  end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
tjwallace
fonte
105

ExiRe escreveu:

Esse comportamento do rubi é realmente frustrante. Quero dizer, se você passar para a seção particular self.method, NÃO será privado. Mas se você o move para a classe << self, então de repente funciona. É apenas nojento.

Confundir provavelmente é frustrante, mas pode ser nojento, mas definitivamente não é.

Faz todo o sentido quando você entende o modelo de objeto de Ruby e o fluxo de pesquisa do método correspondente , especialmente quando se considera que NÃOprivate é um modificador de acesso / visibilidade, mas na verdade uma chamada de método (com a classe como destinatária), conforme discutido aqui ... não existe uma seção particular no Ruby.

Para definir métodos de instância privada , chame privatea classe da instância para definir a visibilidade padrão para métodos subsequentemente definidos como private ... e, portanto, faz todo o sentido definir métodos de classe privada chamando privatea classe da classe, ou seja. sua metaclasse.

Outras linguagens OO auto-proclamadas tradicionais podem fornecer uma sintaxe menos confusa, mas você definitivamente troca isso contra um modelo de objeto confuso e menos consistente (inconsistente?) Sem o poder dos recursos de metaprogramação de Ruby.

pvandenberk
fonte
Então, se eu entendi direito, o próprio ruby ​​não tem palavras-chave de modificador de acesso (público, privado e protegido), mas possui métodos modificadores de acesso (público, privado, protegido)? Isso é algo que deve ser trazido ao rastreador de bug ruby ​​para o Matz implementar modificadores adequados de acesso a palavras-chave ou esse comportamento é esperado?
Edward
13
@Edward Foi projetado dessa maneira junichiito.blogspot.co.uk/2012/03/… . Por que "adequado"?
iain
1
Na sequência disso, em vez de fazer, private_class_method :method_namevocê poderia fazer private_class_method def method_name....
bjt38
send(private_method)também é acessível fora do objeto.
stevenspiel
1
@ bjt38 apenas para maior clareza, seriaprivate_class_method def self.method_name
Tom
77

Por padrão, todos os métodos de classe são públicos. Para torná-los privados, você pode usar o Módulo # private_class_method como @tjwallace escreveu ou defini-los de maneira diferente, como você fez:

class << self

  private

  def method_name
    ...
  end
end

class << selfabre a classe singleton do self, para que os métodos possam ser redefinidos para o atual objeto próprio. Isso é usado para definir o método de classe / módulo ("estático"). Somente lá, definir métodos privados realmente fornece métodos de classe privada.

roxxypoxxy
fonte
17

Apenas pela integridade, também podemos evitar a declaração de private_class_method em uma linha separada. Eu pessoalmente não gosto desse uso, mas é bom saber que ele existe.

private_class_method  def self.method_name
 ....
end
Emre Basala
fonte
5

Eu também acho Ruby (ou pelo menos o meu conhecimento disso) aquém da marca nesta área. Por exemplo, o seguinte faz o que eu quero, mas é desajeitado,

class Frob
    attr_reader :val1, :val2

    Tolerance = 2 * Float::EPSILON

    def initialize(val1, val2)
        @val2 = val1
        @val2 = val2
        ...
    end

    # Stuff that's likely to change and I don't want part
    # of a public API.  Furthermore, the method is operating
    # solely upon 'reference' and 'under_test' and will be flagged as having
    # low cohesion by quality metrics unless made a class method.
    def self.compare(reference, under_test)
        # special floating point comparison
        (reference - under_test).abs <= Tolerance
    end
    private_class_method :compare

    def ==(arg)
        self.class.send(:compare, val1, arg.val1) &&
        self.class.send(:compare, val2, arg.val2) &&
        ...
    end
end

Meus problemas com o código acima é que os requisitos de sintaxe do Ruby e minhas métricas de qualidade de código conspiram para criar código complicado. Para que o código funcione como eu quero e silencie as métricas, devo fazer do compare () um método de classe. Como não quero que faça parte da API pública da classe ', preciso que ela seja privada, mas' privada 'por si só não funciona. Em vez disso, sou forçado a usar 'private_class_method' ou alguma solução alternativa. Por sua vez, isso força o uso de 'self.class.send (: compare ...' para cada variável que eu testo em '== ()'. Agora isso é um pouco complicado.

dinman2022
fonte
O fato de você precisar usar o send não tem nada a ver com o "como" você marca os métodos de classe como particulares. Métodos particulares não podem ser chamados de "fora".
Pascal
4

Os métodos de instância são definidos dentro de um bloco de definição de classe. Os métodos de classe são definidos como métodos singleton na classe singleton de uma classe, também conhecidos informalmente como "metaclasse" ou "autoclasse". privatenão é uma palavra-chave, mas um método ( Módulo # privado ).

Esta é uma chamada ao método self#private/ na A#privatequal "alterna" o acesso privado para todas as próximas definições de método de instância até alternar de outra forma:

class A
  private
    def instance_method_1; end
    def instance_method_2; end
    # .. and so forth
end

Como observado anteriormente, os métodos de classe são realmente métodos singleton definidos na classe singleton.

def A.class_method; end

Ou usando uma sintaxe especial para abrir o corpo da definição da classe anônima e singleton de A:

class << A
  def class_method; end
end

O receptor da "mensagem privada" - self -inside class Aé o objeto de classe A. self dentro do class << Abloco é outro objeto, a classe singleton.

O exemplo a seguir está na realidade chamando dois métodos diferentes chamados private , usando dois destinatários ou destinos diferentes para a chamada. Na primeira parte, definimos um método de instância privada ("na classe A"); na segunda, definimos um método de classe privada (na verdade, é um método singleton no objeto de classe singleton de A).

class A
  # self is A and private call "A.private()"
  private def instance_method; end

  class << self
    # self is A's singleton class and private call "A.singleton_class.private()"
    private def class_method; end
  end
end

Agora, reescreva este exemplo um pouco:

class A
  private
    def self.class_method; end
end

Você consegue ver o erro [que os designers de linguagem Ruby] cometeram? Você ativa o acesso privado para todos os métodos de instância futuros de A, mas continua a declarar um método singleton em uma classe diferente, a classe singleton.

Martin Andersson
fonte
-1

Ruby parece fornecer uma solução ruim. Para explicar, comece com um exemplo simples de C ++ que mostra o acesso a métodos de classe privada:

#include <iostream>

class C
{
    public:
        void instance_method(void)
        {
            std::cout << "instance method\n";
            class_method();  // !!! LOOK !!! no 'send' required. We can access it
                             // because 'private' allows access within the class
        }
    private:
        void static class_method(void) { std::cout << "class method\n"; }
};

int main()
{
    C c;

    c.instance_method(); // works
    // C::class_method() does not compile - it's properly private
    return 0;
}

Executando o acima

   % ./a.out
   instance method
   class method

Agora, Ruby não parece fornecer o equivalente. As regras de Ruby, eu acho, são que métodos privados não devem ser acessados ​​com um receptor. Isso é,

inst.pvt_method  # FAILS
pvt_method # WORKS only within the class (good)

Isso é bom para métodos de instância privada, mas causa problemas com métodos de classe privada.

Eu gostaria que Ruby funcionasse dessa maneira:

class C
    def instance_method
        STDOUT << "instance method\n"

        # Simple access to the private class method would be nice:
        class_method   # DOES NOT WORK. RUBY WON'T FIND THE METHOD
        C.class_method # DOES NOT WORK. RUBY WON'T ALLOW IT

        # ONLY THIS WORKS. While I am happy such capability exists I think
        # the way 'send' should be used is when the coder knows he/she is
        # doing a no-no.  The semantic load on the coder for this is also
        # remarkably clumsy for an elegant language like ruby.
        self.class.send(:class_method)
    end

    private_class_method def self.class_method() STDOUT << "class method\n"; end
end

Mas, infelizmente, o acima exposto não funciona. Alguém sabe uma maneira melhor?

Quando vejo 'send' antes de um método, é um sinal claro de que o código está violando a intenção do designer da API, mas nesse caso o design é especificamente para que um método de instância da classe chame o método de classe privada.

Dave Inman
fonte
-14

A partir do ruby ​​2.3.0

class Check
  def self.first_method
    second_method
  end

  private
  def self.second_method
    puts "well I executed"
  end
end

Check.first_method
#=> well I executed
Vamsi Pavan Mahesh
fonte
Eu estava tentando isso private def self.second_methodantes de cada notação de método, que não estava funcionando no meu ruby ​​2.3.3. Mas essa notação funciona para mim.
Emile Vrijdags
11
Isso está incorreto, porque a chamada Check.second_methodtambém funcionaria sem problemas, por isso não é realmente particular.
Deiwin
1
Não vai funcionar tentar issoprivate_class_method :second_method
KING SABRI