Come creare un metodo di classe privato?


216

Come funziona questo approccio alla creazione di un metodo di classe privata:

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

Ma questo non:

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

7
Ho appena visto questo articolo discutere i modi per creare metodi di classe privata e ho pensato che fosse buono: jakeyesbeck.com/2016/01/24/ruby-private-class-methods/…
Nathan Long

Risposte:


265

privatenon sembra funzionare se stai definendo un metodo su un oggetto esplicito (nel tuo caso self). Puoi usare private_class_methodper definire i metodi di classe come privati ​​(o come hai descritto).

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

In alternativa (in ruby ​​2.1+), poiché una definizione di metodo restituisce un simbolo del nome del metodo, è possibile utilizzare questo come segue:

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

105

ExiRe ha scritto:

Tale comportamento del rubino è davvero frustrante. Voglio dire, se passi alla sezione privata self.method, allora NON è privato. Ma se lo sposti in classe << sé allora all'improvviso funziona. È semplicemente disgustoso.

Confondere probabilmente lo è, può essere frustrante, ma disgustosamente non lo è.

Ha perfettamente senso una volta compreso il modello a oggetti di Ruby e il flusso di ricerca del metodo corrispondente , specialmente se si considera che NONprivate è un modificatore di accesso / visibilità, ma in realtà una chiamata di metodo (con la classe come destinatario) come discusso qui ... non esiste una sezione privata in Ruby.

Per definire i metodi dell'istanza privata , si chiama privatela classe dell'istanza per impostare la visibilità predefinita per i metodi successivamente definiti su privato ... e quindi ha perfettamente senso definire i metodi della classe privata chiamando privatela classe della classe, ad es. la sua metaclasse.

Altri linguaggi OO tradizionali e autoproclamati possono darti una sintassi meno confusa, ma sicuramente lo scambi con un modello a oggetti confuso e meno coerente (incoerente?) Senza il potere delle strutture di metaprogrammazione di Ruby.


Quindi, se ho capito bene, il rubino stesso non ha parole chiave modificatore di accesso (pubblico, privato e protetto) ma piuttosto ha metodi modificatore di accesso (pubblico, privato, protetto)? È qualcosa che dovrebbe essere menzionato nel tracker di bug ruby ​​per Matz per implementare modificatori di accesso alle parole chiave appropriati o è questo comportamento previsto?
Edward

13
@Edward È progettato in questo modo junichiito.blogspot.co.uk/2012/03/… . Perché "corretto"?
Iain

1
Seguendo questo, invece di farlo, private_class_method :method_namepotresti farlo private_class_method def method_name....
bjt38,

send(private_method)è accessibile anche al di fuori dell'oggetto.
Stevenspiel,

1
@ bjt38 Solo per chiarezza, sarebbeprivate_class_method def self.method_name
Tom,

78

Per impostazione predefinita, tutti i metodi di classe sono pubblici. Per renderli privati ​​puoi usare il Modulo # private_class_method come @tjwallace ha scritto o definirli in modo diverso, come hai fatto:

class << self

  private

  def method_name
    ...
  end
end

class << selfapre la classe singleton di sé, in modo che i metodi possano essere ridefiniti per l'oggetto auto corrente. Questo è usato per definire il metodo class / module ("statico"). Solo lì, la definizione di metodi privati ​​ti dà davvero metodi di classe privati.


17

Solo per completezza, possiamo anche evitare di dichiarare private_class_method in una riga separata. Personalmente non mi piace questo utilizzo ma è bello sapere che esiste.

private_class_method  def self.method_name
 ....
end

5

Anch'io trovo Ruby (o almeno la mia conoscenza di esso) a corto di segno in questa zona. Ad esempio, quanto segue fa quello che voglio ma è goffo,

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

I miei problemi con il codice sopra riportato sono che i requisiti di sintassi di Ruby e le mie metriche sulla qualità del codice cospirano per rendere il codice ingombrante. Per fare in modo che il codice funzioni come desidero e per attenuare le metriche, devo confrontare () un metodo di classe. Dal momento che non voglio che faccia parte dell'API pubblica della classe, ho bisogno che sia privato, ma 'privato' da solo non funziona. Invece sono costretto a usare 'private_class_method' o qualche soluzione simile. Questo, a sua volta, impone l'uso di "self.class.send (: compare ..." per ogni variabile che testo in "== ()". Ora è un po 'ingombrante.


Il fatto che sia necessario utilizzare send non ha nulla a che fare con il "come" si contrassegnano i metodi di classe come privati. I metodi privati ​​non possono essere chiamati dall'esterno.
Pascal,

4

I metodi di istanza sono definiti all'interno di un blocco di definizione di classe. I metodi di classe sono definiti come metodi singleton sulla classe singleton di una classe, nota anche in modo informale come "metaclasse" o "eigenclass". privatenon è una parola chiave, ma un metodo ( Modulo # privato ).

Questa è una chiamata al metodo self#private/ A#privateche "attiva" l'accesso privato per tutte le definizioni del metodo dell'istanza imminente fino a quando non viene attivato diversamente:

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

Come notato in precedenza, i metodi di classe sono in realtà metodi singleton definiti sulla classe singleton.

def A.class_method; end

O usando una sintassi speciale per aprire il corpo della definizione della classe anonima e singleton di A:

class << A
  def class_method; end
end

Il destinatario del "messaggio privato" - self - inside class Aè l'oggetto classe A. self all'interno del class << Ablocco è un altro oggetto, la classe singleton.

Il seguente esempio in realtà chiama due diversi metodi chiamati private , usando due diversi destinatari o target per la chiamata. Nella prima parte, definiamo un metodo di istanza privato ("sulla classe A"), in quest'ultima definiamo un metodo di classe privata (è in effetti un metodo singleton sull'oggetto classe singleton di 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

Ora, riscrivi un po 'questo esempio:

class A
  private
    def self.class_method; end
end

Riesci a vedere l'errore [che i progettisti del linguaggio Ruby] hanno fatto? Attivare l'accesso privato per tutti i prossimi metodi di istanza di A, ma si procede a dichiarare un metodo singleton su una classe diversa, la classe singleton.


-1

Ruby sembra fornire una soluzione scadente. Per spiegare, inizia con un semplice esempio C ++ che mostra l'accesso ai metodi di classe privata:

#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;
}

Eseguendo quanto sopra

   % ./a.out
   instance method
   class method

Ora Ruby non sembra fornire l'equivalente. Le regole di Ruby, penso, sono che non è possibile accedere ai metodi privati ​​con un ricevitore. Questo è,

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

Va bene per i metodi di istanza privata, ma causa problemi con i metodi di classe privata.

Vorrei che Ruby funzionasse in questo modo:

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

Ma, ahimè, quanto sopra non funziona. Qualcuno conosce un modo migliore?

Quando vedo "invia" prima di un metodo, è un chiaro segno che il codice sta violando l'intento del progettista dell'API, ma in questo caso il progetto prevede specificamente che un metodo di istanza della classe chiami il metodo della classe privata.


-14

A partire dal rubino 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

Ci stavo provando private def self.second_methodprima di ogni notazione del metodo, che non funzionava sul mio rubino 2.3.3. Ma questa notazione funziona per me.
Emile Vrijdags,

11
Questo non è corretto, perché anche la chiamata Check.second_methodfunzionerebbe senza problemi, quindi non è davvero privata.
Deiwin,

1
Non funzionerà, prova questoprivate_class_method :second_method
KING SABRI,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.