14 Stimmen

Ruby Metaklassen-Wahnsinn

Ich stecke fest. Ich versuche, dynamisch eine Klasse-Methode zu definieren und ich kann nicht wickeln meinen Kopf um die Ruby-Metaklasse-Modell. Betrachten Sie die folgende Klasse:

class Example

  def self.meta; (class << self; self; end); end

  def self.class_instance; self; end

end

Example.class_instance.class # => Class
Example.meta.class           # => Class

Example.class_instance  == Example      # => true
Example.class_instance  == Example.meta # => false

Offensichtlich geben beide Methoden eine Instanz von Class zurück. Aber diese beiden Instanzen sind nicht dieselben. Sie haben auch unterschiedliche Vorfahren:

Example.meta.ancestors            # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors  # => [Example, Object, Kernel]

Welchen Sinn hat es, einen Unterschied zwischen der Metaklasse und der Klasseninstanz zu machen?

Ich habe herausgefunden, dass ich send :define_method an die Metaklasse, um eine Methode dynamisch zu definieren, aber wenn ich versuche, sie an die Klasseninstanz zu senden, funktioniert es nicht. Zumindest konnte ich mein Problem lösen, aber ich möchte immer noch verstehen, warum es auf diese Weise funktioniert.

Update 15. März 2010 13:40

Sind die folgenden Annahmen richtig?

  • Wenn ich eine Instanzmethode habe, die self.instance_eval aufruft und eine Methode definiert, wirkt sie sich nur auf die jeweilige Instanz dieser Klasse aus.
  • Wenn ich eine Instanzmethode habe, die self.class.instance_eval aufruft (was dasselbe wäre wie der Aufruf von class_eval) und eine Methode definiert, wirkt sich das auf alle Instanzen dieser bestimmten Klasse aus und führt zu einer neuen Instanzmethode.
  • Wenn ich eine Klassenmethode habe, die instance_eval aufruft und eine Methode definiert, wird dies zu einer neuen Instanzmethode für alle Instanzen führen.
  • Wenn ich eine Klassenmethode habe, die instance_eval auf der Meta-/Eigenklasse aufruft und eine Methode definiert, führt dies zu einer Klassenmethode.

Ich glaube, das ergibt langsam einen Sinn für mich. Es würde sicherlich Ihre Möglichkeiten einschränken, wenn self innerhalb einer Klassenmethode auf die eigene Klasse verweisen würde. In diesem Fall wäre es nicht möglich, eine Instanzmethode innerhalb einer Klassenmethode zu definieren. Ist das richtig?

11voto

khelll Punkte 22794

Eine Singleton-Methode dynamisch zu definieren ist einfach, wenn Sie instance_eval :

Example.instance_eval{ def square(n); n*n; end }
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27

Was den oben genannten Unterschied betrifft, so verwechseln Sie 2 Dinge:

Die von Ihnen definierte Metaklasse wird in der Ruby-Gemeinschaft als Singelton-Klasse ou Eigenklasse . Diese Singleton-Klasse ist die Klasse, der Sie class(singleton)-Methoden hinzufügen können.

Was die Klasseninstanz betrifft, die Sie mit der Option class_instance Methode, ist nichts anderes als die Klasse selbst. Um das zu beweisen, fügen Sie einfach eine Instanzmethode zur Klasse Example und prüfen Sie, ob die class_instance von Ihnen definierte Methode gibt die Klasse Example selbst, indem sie das Vorhandensein dieser Methode überprüft:

class Example
  def self.meta; (class << self; self; end); end
  def self.class_instance; self; end
  def hey; puts hey; end
end

Example.class_instance.instance_methods(false) #=> ['hey']

Wie auch immer, um es für Sie zusammenzufassen, wenn Sie Klassenmethoden hinzufügen wollen, fügen Sie sie einfach zu dieser Metaklasse hinzu. Was die class_instance Methode ist nutzlos, entfernen Sie sie einfach.

Auf jeden Fall empfehle ich Ihnen die Lektüre diese Stelle einige Konzepte des Ruby-Reflexionssystems zu begreifen.

UPDATE

Ich empfehle Ihnen, diesen schönen Beitrag zu lesen: Spaß mit Ruby's instance_eval und class_eval , Leider class_eval y instance_eval sind verwirrend, weil sie irgendwie gegen ihre Benennung arbeiten!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

Nun zu Ihren Annahmen:

Wenn ich eine Instanzmethode habe, die self.instance_eval aufruft und eine Methode definiert, wirkt sich das nur auf die bestimmte Instanz dieser Klasse.

ja:

class Foo
  def assumption1()
    self.instance_eval("def test_assumption_1; puts 'works'; end")
  end
end

f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []

Wenn ich eine Instanzmethode habe, die self.class.instance_eval aufruft (was wäre dasselbe wie der Aufruf von class_eval) und eine Methode definiert, dann auf alle Instanzen dieser bestimmten Klasse, was zu einer neuen Instanz-Methode.

keine instance_eval definiert in diesem Kontext Singleton-Methoden (keine Instanzmethoden) für die Klasse selbst:

class Foo
  def assumption2()
    self.class.instance_eval("def test_assumption_2; puts 'works'; end")
  end
end

f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]

Damit das funktioniert, ersetzen Sie instance_eval con class_eval oben.

Wenn ich eine Klassenmethode habe, die Folgendes aufruft instance_eval aufruft und eine Methode definiert, die wird eine neue Instanzmethode für alle Instanzen.

Nö:

class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

Foo.singleton_methods(false) #=> ["assumption_3"]

Dadurch entstehen Singleton-Methoden, keine Instanzmethoden. Damit das funktioniert, ersetzen Sie instance_eval con class_eval oben.

Wenn ich eine Klassenmethode habe, die Folgendes aufruft instance_eval für die Meta-/Eigenklasse aufruft und eine Methode definiert, ergibt das eine eine Klassenmethode.

Nun, nein, das wird so anspruchsvolles Zeug machen, wie es Singleton-Methode der Singleton-Klasse hinzufügen wird, ich glaube nicht, dass das irgendeinen praktischen Nutzen haben wird.

5voto

molf Punkte 70728

Wenn Sie eine Methode auf einer Klasse kann es auf seiner Website aufgerufen werden. Objekte . Es ist ein Fallmethode .

class Example
end

Example.send :define_method, :foo do
  puts "foo"
end

Example.new.foo
#=> "foo"

Wenn Sie eine Methode auf einer Metaklasse kann es auf der Seite Klasse . Dies ist vergleichbar mit dem Konzept einer Klassenmethode oder statische Methode in anderen Sprachen.

class Example
  def self.metaclass
    class << self
      self
    end
  end
end

Example.metaclass.send :define_method, :bar do
  puts "bar"
end

Example.bar
#=> "bar"

En Grund dass es Metaklassen gibt, weil man dies in Ruby tun kann:

str = "hello"
class << str
  def output
    puts self
  end
end

str.output
#=> "hello"

"hi".output
# NoMethodError

Wie Sie sehen können, haben wir eine Methode definiert, die nur verfügbar ist für eine Instanz eines Strings. Das Ding, für das wir diese Methode definiert haben, heißt Metaklasse . In der Kette der Methodensuche wird zuerst auf die Metaklasse zugegriffen, bevor die Klasse des Objekts gesucht wird.

Wenn wir das Objekt vom Typ String mit einem Objekt des Typs Class können Sie sich vorstellen, warum dies bedeutet, dass wir nur eine Methode auf einer spezifisch Klasse, nicht auf alle Klassen.

Die Unterschiede zwischen dem aktuellen Kontext und self subtil sind, können Sie mehr lesen wenn Sie daran interessiert sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X