Ruby Language
Verfeinerungen
Suche…
Bemerkungen
Verfeinerungen sind lexikalisch, dh sie sind ab dem Zeitpunkt der Aktivierung (mit Schlüsselwort using
) wirksam, bis sich die Kontrolle ändert. Normalerweise wird die Kontrolle am Ende eines Moduls, einer Klasse oder einer Datei geändert.
Affenflicken mit begrenztem Umfang
Das Hauptproblem von Monkey Patching ist, dass es den globalen Geltungsbereich verschmutzt. Ihr Code ist von allen Modulen abhängig, die Sie verwenden, ohne sich gegenseitig auf die Zehen zu treten. Die Ruby-Lösung hierfür sind Verfeinerungen, bei denen es sich im Wesentlichen um Affen-Patches in einem begrenzten Umfang handelt.
module Patches
refine Fixnum do
def plus_one
self + 1
end
def plus(num)
self + num
end
def concat_one
self.to_s + '1'
end
end
end
class RefinementTest
# has access to our patches
using Patches
def initialize
puts 1.plus_one
puts 3.concat_one
end
end
# Main scope doesn't have changes
1.plus_one
# => undefined method `plus_one' for 1:Fixnum (NoMethodError)
RefinementTest.new
# => 2
# => '31'
Dual-Purpose-Module (Verfeinerungen oder globale Patches)
Es empfiehlt sich, Patches mit Hilfe von Refinements zu erweitern, aber manchmal ist es schön, sie global zu laden (zum Beispiel in der Entwicklung oder beim Testen).
Angenommen, Sie möchten eine Konsole starten, benötigen Ihre Bibliothek und haben dann die gepatchten Methoden im globalen Bereich verfügbar. Sie können dies nicht mit Verfeinerungen tun, da using
in einer Klassen- / Moduldefinition aufgerufen using
muss. Es ist jedoch möglich, den Code so zu schreiben, dass er einen doppelten Zweck hat:
module Patch
def patched?; true; end
refine String do
include Patch
end
end
# globally
String.include Patch
"".patched? # => true
# refinement
class LoadPatch
using Patch
"".patched? # => true
end
Dynamische Verfeinerungen
Verfeinerungen unterliegen besonderen Einschränkungen.
refine
kann nur in einem Modulbereich verwendet werden, kann aber mit send :refine
programmiert werden.
using
ist eher begrenzt. Es kann nur in einer Klassen- / Moduldefinition aufgerufen werden. Es kann jedoch eine Variable akzeptieren, die auf ein Modul zeigt, und kann in einer Schleife aufgerufen werden.
Ein Beispiel, das diese Konzepte zeigt:
module Patch
def patched?; true; end
end
Patch.send(:refine, String) { include Patch }
patch_classes = [Patch]
class Patched
patch_classes.each { |klass| using klass }
"".patched? # => true
end
Da die using
so statisch ist, kann eine Ladereihenfolge ausgegeben werden, wenn die Verfeinerungsdateien nicht zuerst geladen werden. Sie können dies beheben, indem Sie die gepatchte Klassen- / Moduldefinition in eine proc einschließen. Zum Beispiel:
module Patch
refine String do
def patched; true; end
end
end
class Foo
end
# This is a proc since methods can't contain class definitions
create_patched_class = Proc.new do
Foo.class_exec do
class Bar
using Patch
def self.patched?; ''.patched == true; end
end
end
end
create_patched_class.call
Foo::Bar.patched? # => true
Beim Aufruf der proc wird die gepatchte Klasse Foo::Bar
. Dies kann verzögert werden, bis der gesamte Code geladen ist.