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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow