Gibt es eine Möglichkeit, eine private Klassenmethode von einer Instanz in Ruby aufzurufen?
-
09-06-2019 - |
Frage
Außer self.class.send :method, args...
, Natürlich.Ich möchte eine ziemlich komplexe Methode sowohl auf Klassen- als auch auf Instanzebene verfügbar machen, ohne den Code zu duplizieren.
AKTUALISIEREN:
@Jonathan Branam:Das war meine Annahme, aber ich wollte sicherstellen, dass niemand sonst einen Ausweg gefunden hatte.Die Sichtbarkeit in Ruby unterscheidet sich stark von der in Java.Da hast du auch völlig Recht private
funktioniert nicht mit Klassenmethoden, obwohl dadurch eine private Klassenmethode deklariert wird:
class Foo
class <<self
private
def bar
puts 'bar'
end
end
end
Foo.bar
# => NoMethodError: private method 'bar' called for Foo:Class
Lösung
Hier ist ein Codeausschnitt, der zu der Frage passt.Die Verwendung von „private“ in einer Klassendefinition gilt nicht für Klassenmethoden.Sie müssen „private_class_method“ wie im folgenden Beispiel verwenden.
class Foo
def self.private_bar
# Complex logic goes here
puts "hi"
end
private_class_method :private_bar
class <<self
private
def another_private_bar
puts "bar"
end
end
public
def instance_bar
self.class.private_bar
end
def instance_bar2
self.class.another_private_bar
end
end
f=Foo.new
f=instance_bar # NoMethodError: private method `private_bar' called for Foo:Class
f=instance_bar2 # NoMethodError: private method `another_private_bar' called for Foo:Class
Ich sehe keinen Weg, das zu umgehen.In der Dokumentation heißt es, dass Sie den Empfang einer privaten Methode nicht angeben können.Außerdem können Sie nur von derselben Instanz aus auf eine private Methode zugreifen.Die Klasse Foo ist ein anderes Objekt als eine bestimmte Instanz von Foo.
Betrachten Sie meine Antwort nicht als endgültig.Ich bin sicherlich kein Experte, aber ich wollte einen Codeausschnitt bereitstellen, damit andere, die versuchen zu antworten, ordnungsgemäß private Klassenmethoden haben.
Andere Tipps
Lassen Sie mich zu dieser Liste mehr oder weniger seltsamer Lösungen und Nichtlösungen beitragen:
puts RUBY_VERSION # => 2.1.2
class C
class << self
private def foo
'Je suis foo'
end
end
private define_method :foo, &method(:foo)
def bar
foo
end
end
puts C.new.bar # => Je suis foo
puts C.new.foo # => NoMethodError
Wenn Ihre Methode lediglich eine ist Utility-Funktion (das heißt, es ist nicht auf Instanzvariablen angewiesen), Sie könnten die Methode in ein Modul einfügen und include
Und extend
die Klasse, sodass sie sowohl als private Klassenmethode als auch als private Instanzmethode verfügbar ist.
Heutzutage braucht man die Hilfsmethoden nicht mehr.Sie können sie einfach in Ihre Methodendefinition integrieren.Das dürfte den Java-Leuten sehr bekannt vorkommen:
class MyClass
private_class_method def self.my_private_method
puts "private class method"
end
private def my_private_method
puts "private instance method"
end
end
Und nein, Sie können eine private Klassenmethode nicht von einer Instanzmethode aus aufrufen.Sie könnten jedoch stattdessen das implementieren Privat Klassenmethode als öffentlich Klassenmethode in a Privat stattdessen eine verschachtelte Klasse mit der private_constant
Hilfsmethode.Sehen dieser Blogbeitrag für weitere Einzelheiten.
Dies ist die Art und Weise, mit „echten“ Methoden privater Klassen zu spielen.
class Foo
def self.private_bar
# Complex logic goes here
puts "hi"
end
private_class_method :private_bar
class <<self
private
def another_private_bar
puts "bar"
end
end
public
def instance_bar
self.class.private_bar
end
def instance_bar2
self.class.another_private_bar
end
def calling_private_method
Foo.send :another_private_bar
self.class.send :private_bar
end
end
f=Foo.new
f.send :calling_private_method
# "bar"
# "hi"
Foo.send :another_private_bar
# "bar"
Prost
Dies ist wahrscheinlich die „nativste Vanilla Ruby“-Methode:
class Foo
module PrivateStatic # like Java
private def foo
'foo'
end
end
extend PrivateStatic
include PrivateStatic
def self.static_public_call
"static public #{foo}"
end
def public_call
"instance public #{foo}"
end
end
Foo.static_public_call # 'static public foo'
Foo.new.public_call # 'instance public foo'
Foo.foo # NoMethodError: private method `foo' called for Foo:Class
Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x00007fa154d13f10>
Mit etwas Ruby-Metaprogrammierung könnten Sie es sogar so aussehen lassen:
class Foo
def self.foo
'foo'
end
extend PrivateStatic
private_static :foo
end
Rubys Metaprogrammierung ist ziemlich leistungsfähig, sodass Sie alle gewünschten Scoping-Regeln technisch implementieren können.Davon abgesehen würde ich immer noch die Klarheit bevorzugen minimale Überraschung der ersten Variante.
Sofern ich das nicht falsch verstehe, brauchen Sie nicht einfach so etwas:
class Foo
private
def Foo.bar
# Complex logic goes here
puts "hi"
end
public
def bar
Foo.bar
end
end
Natürlich könnten Sie die zweite Definition ändern, um Ihren self.class.send-Ansatz zu verwenden, wenn Sie eine harte Codierung des Klassennamens vermeiden möchten ...