¿Cómo crear un método de clase privada?

216

¿Cómo funciona este enfoque de crear un método de clase privada?

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)"

Pero esto no:

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 millas
fuente
77
Acabo de ver este artículo discutiendo formas de crear métodos de clase privada y pensé que era bueno: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/…
Nathan Long

Respuestas:

265

privateno parece funcionar si está definiendo un método en un objeto explícito (en su caso self). Puede usar private_class_methodpara definir métodos de clase como privados (o como usted describió).

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

Alternativamente (en ruby ​​2.1+), dado que la definición de un método devuelve un símbolo del nombre del método, también puede usar esto de la siguiente manera:

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
fuente
105

ExiRe escribió:

Tal comportamiento del rubí es realmente frustrante. Quiero decir, si te mueves a la sección privada self.method, entonces NO es privado. Pero si lo mueve a la clase << self, entonces de repente funciona. Es simplemente asqueroso.

Probablemente sea confuso, frustrante puede ser, pero asqueroso definitivamente no lo es.

Tiene mucho sentido una vez que comprenda el modelo de objetos de Ruby y el flujo de búsqueda del método correspondiente , especialmente cuando se considera que NOprivate es un modificador de acceso / visibilidad, sino una llamada al método (con la clase como su destinatario) como se discute aquí ... No hay tal cosa como "una sección privada" en Ruby.

Para definir métodos de instancias privadas , llama privatea la clase de la instancia para establecer la visibilidad predeterminada para los métodos definidos posteriormente como privados ... y, por lo tanto, tiene mucho sentido definir métodos de clases privadas llamando privatea la clase de la clase, es decir. su metaclase

Otros lenguajes OO autoproclamados convencionales pueden darle una sintaxis menos confusa, pero definitivamente lo intercambia con un modelo de objeto confuso y menos consistente (¿inconsistente?) Sin el poder de las instalaciones de metaprogramación de Ruby.

pvandenberk
fuente
Entonces, si entiendo correctamente, ¿Ruby no tiene palabras clave modificadoras de acceso (públicas, privadas y protegidas) sino que tiene métodos modificadores de acceso (públicos, privados, protegidos)? ¿Es esto algo que debería mencionarse en el rastreador de errores de ruby ​​para que Matz implemente modificadores de acceso a palabras clave adecuados o es este comportamiento esperado?
Edward
13
@Edward Está diseñado de esa manera junichiito.blogspot.co.uk/2012/03/… . ¿Por qué "apropiado"?
desde el
1
Siguiendo con esto, en lugar de hacerlo private_class_method :method_name, podrías hacer private_class_method def method_name....
bjt38
send(private_method)También es accesible fuera del objeto.
stevenspiel
1
@ bjt38 Solo por claridad, seríaprivate_class_method def self.method_name
Tom
78

Por defecto, todos los métodos de clase son públicos. Para hacerlos privados, puede usar el Módulo # private_class_method como @tjwallace escribió o definirlos de manera diferente, como lo hizo:

class << self

  private

  def method_name
    ...
  end
end

class << selfabre la clase singleton de self para que los métodos se puedan redefinir para el self object actual. Esto se utiliza para definir el método de clase / módulo ("estático"). Solo allí, la definición de métodos privados realmente le brinda métodos de clase privada.

roxxypoxxy
fuente
17

Solo por lo completo, también podemos evitar declarar private_class_method en una línea separada. Personalmente no me gusta este uso, pero es bueno saber que existe.

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

Yo también, encuentro Ruby (o al menos mi conocimiento de él) por debajo de la marca en esta área. Por ejemplo, lo siguiente hace lo que quiero pero es torpe,

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

Mis problemas con el código anterior es que los requisitos de sintaxis de Ruby y las métricas de calidad de mi código conspiran para crear un código engorroso. Para que el código funcione como quiero y para silenciar las métricas, debo hacer que compare () sea un método de clase. Como no quiero que sea parte de la clase 'API pública', necesito que sea privada, pero 'privado' por sí solo no funciona. En cambio, me veo obligado a usar 'private_class_method' o alguna solución alternativa. Esto, a su vez, fuerza el uso de 'self.class.send (: compare ...' para cada variable que pruebo en '== ()'. Ahora eso es un poco difícil de manejar.

dinman2022
fuente
El hecho de que necesite usar send no tiene nada que ver con el "cómo" marca los métodos de clase como privados. Los métodos privados no se pueden llamar desde el "exterior".
Pascal
4

Los métodos de instancia se definen dentro de un bloque de definición de clase. Los métodos de clase se definen como métodos singleton en la clase singleton de una clase, también conocida informalmente como "metaclase" o "clase propia". privateno es una palabra clave, sino un método ( Módulo # privado ).

Esta es una llamada al método self#private/ A#privateque "activa" el acceso privado para todas las próximas definiciones de métodos de instancia hasta que se active de otra manera:

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

Como se señaló anteriormente, los métodos de clase son realmente métodos singleton definidos en la clase singleton.

def A.class_method; end

O utilizando una sintaxis especial para abrir el cuerpo de definición de la clase anónima singleton de A:

class << A
  def class_method; end
end

El receptor del "mensaje privado" - self - inside class Aes el objeto de clase A. self dentro del class << Abloque es otro objeto, la clase singleton.

El siguiente ejemplo es en realidad llamar a dos métodos diferentes llamados privados , utilizando dos destinatarios u objetivos diferentes para la llamada. En la primera parte, definimos un método de instancia privada ("en la clase A"), en la última definimos un método de clase privada (de hecho, es un método singleton en el objeto de clase 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

Ahora, reescribe este ejemplo un poco:

class A
  private
    def self.class_method; end
end

¿Puedes ver el error que cometieron [los diseñadores de lenguaje Ruby]? Alterna el acceso privado para todos los métodos de instancia próximos de A, pero procede a declarar un método singleton en una clase diferente, la clase singleton.

Martin Andersson
fuente
-1

Ruby parece proporcionar una solución pobre. Para explicar, comience con un ejemplo simple de C ++ que muestre el acceso a métodos de clase 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;
}

Ejecutando lo anterior

   % ./a.out
   instance method
   class method

Ahora Ruby no parece proporcionar el equivalente. Las reglas de Ruby, creo, son que no se debe acceder a los métodos privados con un receptor. Es decir,

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

Eso está bien para los métodos de instancia privada, pero causa problemas con los métodos de clase privada.

Me gustaría que Ruby funcionara de esta manera:

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

Pero, por desgracia, lo anterior no funciona. ¿Alguien sabe una mejor manera?

Cuando veo 'enviar' antes de un método, es una señal clara de que el código está violando la intención del diseñador de la API, pero en este caso el diseño es específicamente para que un método de instancia de la clase llame al método de clase privada.

Dave Inman
fuente
-14

A partir de 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
fuente
Intenté esto private def self.second_methodantes de cada notación de método, que no funcionaba en mi ruby ​​2.3.3. Pero esta notación funciona para mí.
Emile Vrijdags
11
Esto es incorrecto, porque las llamadas Check.second_methodtambién funcionarían sin problemas, por lo que no es realmente privado.
Deiwin
1
No funcionará intente estoprivate_class_method :second_method
KING SABRI