Wie erstelle ich eine private Klassenmethode?

216

Wie kommt es, dass dieser Ansatz zum Erstellen einer privaten Klassenmethode funktioniert:

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

Das geht aber nicht:

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 Meilen
quelle
7
Ich habe gerade diesen Artikel gesehen, in dem Möglichkeiten zum Erstellen von Methoden für Privatklassen besprochen wurden,
Nathan Long

Antworten:

265

privatescheint nicht zu funktionieren, wenn Sie eine Methode für ein explizites Objekt definieren (in Ihrem Fall self). Sie können private_class_methodKlassenmethoden als privat definieren (oder wie beschrieben).

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

Alternativ (in Ruby 2.1+) können Sie dies auch wie folgt verwenden, da eine Methodendefinition ein Symbol des Methodennamens zurückgibt:

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

ExiRe schrieb:

Ein solches Verhalten von Rubin ist wirklich frustrierend. Ich meine, wenn Sie in den privaten Bereich self.method wechseln, ist er NICHT privat. Aber wenn Sie es in die Klasse << self verschieben, funktioniert es plötzlich. Es ist einfach ekelhaft.

Es ist wahrscheinlich verwirrend, frustrierend mag es sein, aber ekelhaft ist es definitiv nicht.

Es ist absolut sinnvoll, wenn Sie Rubys Objektmodell und den entsprechenden Methodensuchablauf verstanden haben , insbesondere wenn Sie berücksichtigen, dass privatees sich NICHT um einen Zugriffs- / Sichtbarkeitsmodifikator handelt, sondern um einen Methodenaufruf (mit der Klasse als Empfänger), wie hier beschrieben ... In Ruby gibt es keinen "privaten Bereich" .

Um private Instanzmethoden zu definieren , rufen Sie privatedie Klasse der Instanz auf, um die Standardsichtbarkeit für nachfolgend definierte Methoden auf privat festzulegen. Daher ist es sinnvoll, private Klassenmethoden durch Aufrufen privateder Klasse der Klasse zu definieren, d. H. seine Metaklasse.

Andere selbsternannte OO-Hauptsprachen geben Ihnen möglicherweise eine weniger verwirrende Syntax, aber Sie tauschen dies definitiv gegen ein verwirrendes und weniger konsistentes (inkonsistentes?) Objektmodell aus, ohne die Leistungsfähigkeit von Rubys Metaprogrammierfunktionen.

pvandenberk
quelle
Wenn ich das richtig verstehe, hat Ruby selbst keine Zugriffsmodifikator-Schlüsselwörter (öffentlich, privat und geschützt), sondern Zugriffsmodifikator-Methoden (öffentlich, privat, geschützt)? Ist dies etwas, das auf dem Ruby Bug Tracker für Matz angezeigt werden sollte, um die richtigen Keyword-Zugriffsmodifikatoren zu implementieren, oder ist dies das erwartete Verhalten?
Edward
13
@Edward ist es entworfen , auf diese Weise junichiito.blogspot.co.uk/2012/03/... . Warum "richtig"?
iain
1
Im Anschluss daran private_class_method :method_namekönnten Sie es tun , anstatt es zu tun private_class_method def method_name....
bjt38
send(private_method)ist auch außerhalb des Objekts zugänglich.
Stevenspiel
1
@ bjt38 Nur zur Klarheit wäre esprivate_class_method def self.method_name
Tom
77

Standardmäßig sind alle Klassenmethoden öffentlich. Um sie privat zu machen, können Sie Modul # private_class_method verwenden, wie @tjwallace sie geschrieben oder anders definiert hat, als Sie es getan haben:

class << self

  private

  def method_name
    ...
  end
end

class << selfÖffnet die Singleton-Klasse von self, sodass Methoden für das aktuelle self-Objekt neu definiert werden können. Dies wird verwendet, um die Klassen- / Modulmethode ("statisch") zu definieren. Nur dort erhalten Sie durch das Definieren privater Methoden wirklich private Klassenmethoden.

roxxypoxxy
quelle
17

Der Vollständigkeit halber können wir auch vermeiden, private_class_method in einer separaten Zeile zu deklarieren. Ich persönlich mag diese Verwendung nicht, aber es ist gut zu wissen, dass sie existiert.

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

Auch ich finde Ruby (oder zumindest mein Wissen darüber) in diesem Bereich hinter der Marke zurück. Zum Beispiel macht das Folgende, was ich will, ist aber ungeschickt,

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

Meine Probleme mit dem obigen Code bestehen darin, dass die Ruby-Syntaxanforderungen und meine Codequalitätsmetriken zu umständlichem Code führen. Damit der Code wie gewünscht funktioniert und die Metriken beruhigt werden, muss compare () zu einer Klassenmethode gemacht werden. Da ich nicht möchte, dass es Teil der öffentlichen API der Klasse ist, muss es privat sein, aber "privat" allein funktioniert nicht. Stattdessen bin ich gezwungen, 'private_class_method' oder eine solche Problemumgehung zu verwenden. Dies erzwingt wiederum die Verwendung von 'self.class.send (: compare ...' für jede Variable, die ich in '== ()' teste. Nun, das ist etwas unhandlich.

dinman2022
quelle
Die Tatsache, dass Sie send verwenden müssen , hat nichts mit dem "Wie" zu tun, mit dem Sie Klassenmethoden als privat markieren. Private Methoden können nicht von "außen" aufgerufen werden.
Pascal
4

Instanzmethoden werden in einem Klassendefinitionsblock definiert. Klassenmethoden werden als Singleton-Methoden für die Singleton-Klasse einer Klasse definiert, die informell auch als "Metaklasse" oder "Eigenklasse" bezeichnet wird. privateist kein Schlüsselwort, sondern eine Methode ( Modul # privat ).

Dies ist ein Aufruf von method self#private/, A#privateder den privaten Zugriff für alle bevorstehenden Instanzmethodendefinitionen "umschaltet", bis etwas anderes umgeschaltet wird:

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

Wie bereits erwähnt, sind Klassenmethoden wirklich Singleton-Methoden, die für die Singleton-Klasse definiert sind.

def A.class_method; end

Oder verwenden Sie eine spezielle Syntax, um den Definitionskörper der anonymen Singleton-Klasse von A zu öffnen:

class << A
  def class_method; end
end

Der Empfänger der "Nachricht privat" - self - inside class Aist das Klassenobjekt A. self innerhalb des class << ABlocks ist ein weiteres Objekt, die Singleton-Klasse.

Das folgende Beispiel ruft in Wirklichkeit zwei verschiedene Methoden auf, die als privat bezeichnet werden , wobei zwei verschiedene Empfänger oder Ziele für den Anruf verwendet werden. Im ersten Teil definieren wir eine private Instanzmethode ("für Klasse A"), im letzteren definieren wir eine private Klassenmethode (ist tatsächlich eine Singleton-Methode für das Singleton-Klassenobjekt von 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

Schreiben Sie dieses Beispiel jetzt ein wenig um:

class A
  private
    def self.class_method; end
end

Können Sie den Fehler sehen, den Ruby-Sprachdesigner gemacht haben? Sie schalten den privaten Zugriff für alle bevorstehenden Instanzmethoden von A um, deklarieren jedoch eine Singleton-Methode für eine andere Klasse, die Singleton-Klasse.

Martin Andersson
quelle
-1

Ruby scheint eine schlechte Lösung zu bieten. Beginnen Sie zur Erklärung mit einem einfachen C ++ - Beispiel, das den Zugriff auf Methoden privater Klassen zeigt:

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

Führen Sie die oben genannten

   % ./a.out
   instance method
   class method

Jetzt scheint Ruby nicht das Äquivalent zu liefern. Ich denke, Rubys Regeln lauten, dass auf private Methoden nicht mit einem Empfänger zugegriffen werden darf. Das ist,

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

Dies ist für private Instanzmethoden in Ordnung, verursacht jedoch Probleme mit privaten Klassenmethoden.

Ich möchte, dass Ruby so funktioniert:

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

Aber leider funktioniert das oben genannte nicht. Kennt jemand einen besseren Weg?

Wenn ich vor einer Methode 'send' sehe, ist dies ein klares Zeichen dafür, dass der Code die Absicht des API-Designers verletzt. In diesem Fall besteht das Design jedoch speziell darin, dass eine Instanzmethode der Klasse die private Klassenmethode aufruft.

Dave Inman
quelle
-14

Ab 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
quelle
Ich habe dies private def self.second_methodvor jeder Methodennotation versucht , was bei meinem Ruby 2.3.3 nicht funktioniert hat. Aber diese Notation funktioniert bei mir.
Emile Vrijdags
11
Dies ist falsch, da das Anrufen Check.second_methodauch problemlos funktionieren würde und daher nicht wirklich privat ist.
Deiwin
1
Es wird nicht funktionieren, versuchen Sie esprivate_class_method :second_method
KING SABRI