Refinements
Aufgrund von Rubys offenen Klassen können Sie Funktionalitäten bestehender Klassen neu definieren oder hinzufügen. Dies wird als „Monkey Patch“ bezeichnet. Unglücklicherweise ist der Geltungsbereich solcher Änderungen global. Alle Benutzer der Monkey-Patched-Klasse sehen dieselben Änderungen. Dies kann zu unbeabsichtigten Nebeneffekten oder Programmabbrüchen führen.
Refinements wurden entwickelt, um die Auswirkungen von Monkey Patching auf andere Benutzer der Monkey-Patched-Klasse zu reduzieren. Refinements bieten eine Möglichkeit, eine Klasse lokal zu erweitern. Refinements können sowohl Klassen als auch Module ändern.
Hier ist ein grundlegendes Refinement
class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end
Zuerst wird eine Klasse C definiert. Als Nächstes wird ein Refinement für C unter Verwendung von Module#refine erstellt.
Module#refine erstellt ein anonymes Modul, das die Änderungen oder Refinements an der Klasse (C im Beispiel) enthält. self im Refinement-Block ist dieses anonyme Modul, ähnlich wie bei Module#module_eval.
Aktivieren Sie das Refinement mit using
using M c = C.new c.foo # prints "C#foo in M"
Scope
Sie können Refinements auf oberster Ebene sowie innerhalb von Klassen und Modulen aktivieren. Sie können Refinements nicht im Methodengeltungsbereich aktivieren. Refinements sind bis zum Ende der aktuellen Klassen- oder Moduldefinition oder bis zum Ende der aktuellen Datei, wenn sie auf oberster Ebene verwendet werden, aktiv.
Sie können Refinements in einem String aktivieren, der an Kernel#eval übergeben wird. Refinements sind bis zum Ende des Eval-Strings aktiv.
Refinements sind lexikalisch im Geltungsbereich. Refinements sind nur innerhalb eines Geltungsbereichs nach dem Aufruf von using aktiv. Code vor der using-Anweisung hat das Refinement nicht aktiviert.
Wenn die Kontrolle außerhalb des Geltungsbereichs übertragen wird, wird das Refinement deaktiviert. Das bedeutet, dass, wenn Sie eine Datei requiren oder laden oder eine Methode aufrufen, die außerhalb des aktuellen Geltungsbereichs definiert ist, das Refinement deaktiviert wird.
class C end module M refine C do def foo puts "C#foo in M" end end end def call_foo(x) x.foo end using M x = C.new x.foo # prints "C#foo in M" call_foo(x) #=> raises NoMethodError
Wenn eine Methode in einem Geltungsbereich definiert ist, in dem ein Refinement aktiv ist, ist das Refinement aktiv, wenn die Methode aufgerufen wird. Dieses Beispiel erstreckt sich über mehrere Dateien.
c.rb
class C end
m.rb
require "c" module M refine C do def foo puts "C#foo in M" end end end
m_user.rb
require "m" using M class MUser def call_foo(x) x.foo end end
main.rb
require "m_user" x = C.new m_user = MUser.new m_user.call_foo(x) # prints "C#foo in M" x.foo #=> raises NoMethodError
Da das Refinement M in m_user.rb aktiv ist, wo MUser#call_foo definiert ist, ist es auch aktiv, wenn main.rb call_foo aufruft.
Da using eine Methode ist, sind Refinements nur aktiv, wenn sie aufgerufen wird. Hier sind Beispiele dafür, wo ein Refinement M aktiv ist und wo nicht.
In einer Datei
# not activated here using M # activated here class Foo # activated here def foo # activated here end # activated here end # activated here
In einer Klasse
# not activated here class Foo # not activated here def foo # not activated here end using M # activated here def bar # activated here end # activated here end # not activated here
Beachten Sie, dass die Refinements in M **nicht** automatisch aktiviert werden, wenn die Klasse Foo später wieder geöffnet wird.
In eval
# not activated here eval <<EOF # not activated here using M # activated here EOF # not activated here
Wenn nicht evaluiert
# not activated here if false using M end # not activated here
Wenn mehrere Refinements im selben Modul innerhalb mehrerer refine-Blöcke definiert werden, sind alle Refinements desselben Moduls aktiv, wenn eine verfeinerte Methode (eine der unten aufgeführten to_json-Methoden) aufgerufen wird.
module ToJSON refine Integer do def to_json to_s end end refine Array do def to_json "[" + map { |i| i.to_json }.join(",") + "]" end end refine Hash do def to_json "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}" end end end using ToJSON p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]"
Methoden-Lookup
Beim Suchen nach einer Methode für eine Instanz der Klasse C prüft Ruby
-
Die Refinements von
C, in umgekehrter Reihenfolge der Aktivierung -
Die vorangestellten Module von
C -
C -
Die inkludierten Module von
C
Wenn an keiner Stelle eine Methode gefunden wurde, wiederholt sich dies mit der Oberklasse von C.
Beachten Sie, dass Methoden in einer Unterklasse Vorrang vor Refinements in einer Oberklasse haben. Wenn beispielsweise die Methode / in einem Refinement für Numeric definiert ist, ruft 1 / 2 die ursprüngliche Integer#/ auf, da Integer eine Unterklasse von Numeric ist und vor den Refinements für die Oberklasse Numeric durchsucht wird. Da die Methode / auch in der Kindklasse Integer vorhanden ist, bewegt sich der Methoden-Lookup nicht zur Oberklasse nach oben.
Wenn jedoch eine Methode foo auf Numeric in einem Refinement definiert ist, ruft 1.foo diese Methode auf, da foo nicht in Integer existiert.
super
Wenn super aufgerufen wird, prüft der Methoden-Lookup
-
Die inkludierten Module der aktuellen Klasse. Beachten Sie, dass die aktuelle Klasse ein Refinement sein kann.
-
Wenn die aktuelle Klasse ein Refinement ist, wird der Methoden-Lookup wie im Abschnitt „Methoden-Lookup“ oben fortgesetzt.
-
Wenn die aktuelle Klasse eine direkte Oberklasse hat, fährt die Methode wie im Abschnitt „Methoden-Lookup“ oben fort und verwendet die Oberklasse.
Beachten Sie, dass super in einer Methode eines Refinements die Methode in der verfeinerten Klasse aufruft, auch wenn es ein weiteres Refinement gibt, das im selben Kontext aktiviert wurde. Dies gilt nur für super in einer Methode eines Refinements; es gilt nicht für super in einer Methode eines Moduls, das in ein Refinement aufgenommen wurde.
Methoden-Introspektion
Bei der Verwendung von Introspektionsmethoden wie Kernel#method oder Kernel#methods werden Refinements nicht berücksichtigt.
Dieses Verhalten kann in Zukunft geändert werden.
Refinement-Vererbung durch Module#include
Wenn ein Modul X in ein Modul Y aufgenommen wird, erbt Y Refinements von X.
Zum Beispiel erbt C Refinements von A und B im folgenden Code
module A refine X do ... end refine Y do ... end end module B refine Z do ... end end module C include A include B end using C # Refinements in A and B are activated here.
Refinements in Nachkommen haben eine höhere Priorität als die von Vorfahren.
Weitere Lektüre
Siehe github.com/ruby/ruby/wiki/Refinements-Spec für die aktuelle Spezifikation zur Implementierung von Refinements. Die Spezifikation enthält auch weitere Details.