502 Stimmen

Wenn Affen Parcheando eine Instanzmethode sind, können Sie die überschriebene Methode von der neuen Implementierung aus aufrufen?

Angenommen, ich bin ein Affe Parcheando eine Methode in einer Klasse, wie könnte ich die überschriebene Methode von der überschreibenden Methode aufrufen? D.h. etwas in der Art von super

z.B.

class Foo
  def bar()
    "Hello"
  end
end 

class Foo
  def bar()
    super() + " World"
  end
end

>> Foo.new.bar == "Hello World"

1269voto

Jörg W Mittag Punkte 349574

EDIT : Es ist schon 9 Jahre her, dass ich diese Antwort geschrieben habe, und sie verdient eine kleine kosmetische Operation, damit sie aktuell bleibt.

Sie können die letzte Version vor der Bearbeitung sehen aquí .


Sie können nicht die überschrieben Methode nach Name oder Schlüsselwort. Das ist einer der vielen Gründe, warum der Affe Parcheando vermieden und stattdessen die Vererbung bevorzugt werden sollte, da man offensichtlich peut rufen Sie die außer Kraft gesetzt Methode.

Den Affen meiden Parcheando

Vererbung

Wenn es möglich ist, sollten Sie also so etwas vorziehen:

class Foo
  def bar
    'Hello'
  end
end 

class ExtendedFoo < Foo
  def bar
    super + ' World'
  end
end

ExtendedFoo.new.bar # => 'Hello World'

Dies funktioniert, wenn Sie die Erstellung der Foo Objekte. Ändern Sie einfach jede Stelle, die ein Foo um stattdessen eine ExtendedFoo . Dies funktioniert sogar noch besser, wenn Sie die Entwurfsmuster für die Injektion von Abhängigkeiten El Entwurfsmuster für Fabrikmethoden El Abstraktes Fabrikentwurfsmuster oder so ähnlich, denn in diesem Fall gibt es nur eine Stelle, die Sie ändern müssen.

Delegation

Wenn Sie nicht Kontrolle der Erstellung des Foo Objekte, zum Beispiel weil sie von einem Rahmenwerk erstellt werden, das sich Ihrer Kontrolle entzieht (wie ruby-on-rails zum Beispiel), dann könnten Sie die Wrapper-Entwurfsmuster :

require 'delegate'

class Foo
  def bar
    'Hello'
  end
end 

class WrappedFoo < DelegateClass(Foo)
  def initialize(wrapped_foo)
    super
  end

  def bar
    super + ' World'
  end
end

foo = Foo.new # this is not actually in your code, it comes from somewhere else

wrapped_foo = WrappedFoo.new(foo) # this is under your control

wrapped_foo.bar # => 'Hello World'

Grundsätzlich gilt, dass an der Grenze des Systems, wo die Foo Objekt in Ihren Code kommt, verpacken Sie es in ein anderes Objekt und verwenden dann dass Objekt anstelle des Originalobjekts überall sonst in Ihrem Code.

Dabei wird die Object#DelegateClass Helper-Methode aus der delegate Bibliothek in der stdlib.

"Sauberer" Affe Parcheando

Module#prepend : Mixin Voranstellen

Die beiden oben genannten Methoden erfordern eine Änderung des Systems, um den Affen Parcheando zu vermeiden. Dieser Abschnitt zeigt die bevorzugte und am wenigsten invasive Methode von monkey Parcheando, sollte eine Änderung des Systems keine Option sein.

Module#prepend wurde hinzugefügt, um mehr oder weniger genau diesen Anwendungsfall zu unterstützen. Module#prepend tut das Gleiche wie Module#include mit der Ausnahme, dass es das Mixin direkt einmischt unter die Klasse:

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  prepend FooExtensions
end

Foo.new.bar # => 'Hello World'

Anmerkung: Ich habe auch ein wenig über Module#prepend in dieser Frage: Ruby-Modul Prepend vs. Derivation

Mixin-Vererbung (gebrochen)

Ich habe gesehen, dass einige Leute so etwas ausprobiert haben (und hier auf StackOverflow nachgefragt haben, warum es nicht funktioniert), d.h. include ein Mixin anstelle von prepend zu machen:

class Foo
  def bar
    'Hello'
  end
end 

module FooExtensions
  def bar
    super + ' World'
  end
end

class Foo
  include FooExtensions
end

Leider wird das nicht funktionieren. Es ist eine gute Idee, weil es Vererbung verwendet, was bedeutet, dass Sie super . Allerdings, Module#include fügt das Mixin über die Klasse in der Vererbungshierarchie, was bedeutet, dass FooExtensions#bar wird nie aufgerufen (und wenn es waren aufgerufen, die super würde sich eigentlich nicht auf Foo#bar sondern vielmehr auf Object#bar die es nicht gibt), da Foo#bar wird immer zuerst gefunden.

Methode Wrapping

Die große Frage ist: Wie können wir das Vertrauen in die bar Methode, ohne tatsächlich eine eigentliche Methode ? Die Antwort liegt, wie so oft, in der funktionalen Programmierung. Wir fassen die Methode als eine tatsächliche Objekt und wir verwenden einen Abschluss (d. h. einen Block), um sicherzustellen, dass wir und nur wir an diesem Gegenstand festhalten:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  old_bar = instance_method(:bar)

  define_method(:bar) do
    old_bar.bind(self).() + ' World'
  end
end

Foo.new.bar # => 'Hello World'

Dies ist sehr sauber: da old_bar nur eine lokale Variable ist, geht sie am Ende des Klassenkörpers aus dem Geltungsbereich heraus, und es ist unmöglich, von irgendwoher auf sie zuzugreifen, sogar durch Reflexion! Und da Module#define_method nimmt einen Block, und Blöcke schließen sich über ihre lexikalische Umgebung (die warum wir verwenden define_method 代わりに def hier), es (und nur es) werden weiterhin Zugang haben zu old_bar auch wenn sie nicht mehr in den Geltungsbereich fällt.

Kurze Erklärung:

old_bar = instance_method(:bar)

Hier wickeln wir die bar Methode in eine UnboundMethod Methodenobjekt und weisen es der lokalen Variablen old_bar . Das bedeutet, dass wir jetzt die Möglichkeit haben, die bar auch nachdem sie überschrieben wurde.

old_bar.bind(self)

Das ist ein bisschen knifflig. Grundsätzlich ist in Ruby (und in so gut wie allen OO-Sprachen, die auf Single-Dispatch basieren) eine Methode an ein bestimmtes Empfängerobjekt gebunden, das self in Ruby. Mit anderen Worten: eine Methode weiß immer, auf welchem Objekt sie aufgerufen wurde, sie weiß, was ihr self ist. Aber wir haben die Methode direkt aus einer Klasse entnommen, woher weiß sie, was ihre self ist?

Nun, das ist nicht der Fall, und deshalb müssen wir bind unser UnboundMethod in ein Objekt umwandeln, das dann ein Method Objekt, das wir dann aufrufen können. ( UnboundMethod s nicht aufgerufen werden können, weil sie nicht wissen, was sie tun sollen, ohne ihre self .)

Und was wollen wir bind es zu? Wir einfach bind für uns selbst, so wird es sich verhalten genau wie das Original bar hätte!

Zum Schluss müssen wir die Method die zurückgegeben wird von bind . In Ruby 1.9 gibt es dafür eine raffinierte neue Syntax ( .() ), aber wenn Sie auf 1.8 sind, können Sie einfach die call Methode; das ist es, was .() wird sowieso übersetzt.

Hier sind einige weitere Fragen, in denen einige dieser Konzepte erläutert werden:

"Schmutziger" Affe Parcheando

alias_method Kette

Das Problem, das wir mit unserem Affen Parcheando haben, ist, dass, wenn wir die Methode überschreiben, die Methode weg ist, so dass wir sie nicht mehr aufrufen können. Machen wir also einfach eine Sicherungskopie!

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  alias_method :old_bar, :bar

  def bar
    old_bar + ' World'
  end
end

Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'

Das Problem dabei ist, dass wir jetzt den Namensraum mit einem überflüssigen old_bar Methode. Diese Methode wird in unserer Dokumentation auftauchen, sie wird in der Code-Vervollständigung in unseren IDEs auftauchen, sie wird während der Reflexion auftauchen. Außerdem kann sie immer noch aufgerufen werden, aber vermutlich haben wir sie zum Affen gemacht, weil wir ihr Verhalten von Anfang an nicht mochten, so dass wir vielleicht nicht wollen, dass andere sie aufrufen.

Trotz der Tatsache, dass dies einige unerwünschte Eigenschaften hat, wurde es leider durch AciveSupport's Module#alias_method_chain .

Eine Randbemerkung: Verfeinerungen

Falls Sie das abweichende Verhalten nur an einigen bestimmten Stellen und nicht im gesamten System benötigen, können Sie Refinements verwenden, um den Affenpatch auf einen bestimmten Bereich zu beschränken. Ich werde das hier anhand des Module#prepend Beispiel von oben:

class Foo
  def bar
    'Hello'
  end
end 

module ExtendedFoo
  module FooExtensions
    def bar
      super + ' World'
    end
  end

  refine Foo do
    prepend FooExtensions
  end
end

Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!

using ExtendedFoo
# Activate our Refinement

Foo.new.bar # => 'Hello World'
# There it is!

Ein anspruchsvolleres Beispiel für die Verwendung von Verfeinerungen finden Sie in dieser Frage: Wie kann man den Affenpatch für eine bestimmte Methode aktivieren?


Verlassene Ideen

Bevor sich die Ruby-Gemeinschaft auf Module#prepend Es gab mehrere verschiedene Ideen, auf die Sie gelegentlich in älteren Diskussionen Bezug nehmen können. Alle diese Ideen werden zusammengefasst unter Module#prepend .

Methoden-Kombinatoren

Eine Idee war die Idee der Methodenkombinationen aus CLOS. Dies ist im Grunde eine sehr leichtgewichtige Version einer Teilmenge der aspektorientierten Programmierung.

Mit einer Syntax wie

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end

  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

können Sie sich in die Ausführung des Programms "einklinken". bar Methode.

Es ist jedoch nicht ganz klar, ob und wie Sie Zugang zu folgenden Informationen erhalten bar den Rückgabewert innerhalb von bar:after . Vielleicht könnten wir die super Stichwort?

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar:after
    super + ' World'
  end
end

Ersatz

Der Vorher-Kombinator ist äquivalent zu prepend einer Mixin mit einer überschreibenden Methode, die die super bei der sehr Ende der Methode. Ebenso ist der Nach-Kombinator äquivalent zu prepend einer Mixin mit einer überschreibenden Methode, die die super bei der sehr Anfang der Methode.

Sie können auch Dinge tun, bevor und nach dem Aufruf super können Sie anrufen super und sowohl abrufen als auch manipulieren super Rückgabewertes, wodurch prepend leistungsfähiger als Methodenkombinationen.

class Foo
  def bar:before
    # will always run before bar, when bar is called
  end
end

# is the same as

module BarBefore
  def bar
    # will always run before bar, when bar is called
    super
  end
end

class Foo
  prepend BarBefore
end

und

class Foo
  def bar:after
    # will always run after bar, when bar is called
    # may or may not be able to access and/or change bar’s return value
  end
end

# is the same as

class BarAfter
  def bar
    original_return_value = super
    # will always run after bar, when bar is called
    # has access to and can change bar’s return value
  end
end

class Foo
  prepend BarAfter
end

old Stichwort

Diese Idee fügt ein neues Schlüsselwort hinzu, ähnlich wie super die es Ihnen ermöglicht, die überschrieben Methode auf die gleiche Weise super können Sie die außer Kraft gesetzt Methode:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  def bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

Das Hauptproblem dabei ist, dass es nicht rückwärtskompatibel ist: Wenn Sie eine Methode namens old können Sie es nicht mehr anrufen!

Ersatz

super in einer überschreibenden Methode in einer prepend Das Mixin ist im Wesentlichen dasselbe wie old in diesem Vorschlag.

redef Stichwort

Ähnlich wie oben, aber anstatt ein neues Schlüsselwort für aufrufen die überschriebene Methode und die def allein, fügen wir ein neues Schlüsselwort für Neudefinition von Methoden. Dies ist rückwärtskompatibel, da die Syntax derzeit ohnehin illegal ist:

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    old + ' World'
  end
end

Foo.new.bar # => 'Hello World'

Anstelle der Hinzufügung von zwei neuen Schlüsselwörtern, könnten wir auch die Bedeutung von super innerhalb redef :

class Foo
  def bar
    'Hello'
  end
end 

class Foo
  redef bar
    super + ' World'
  end
end

Foo.new.bar # => 'Hello World'

Ersatz

redef einer Methode ist gleichbedeutend mit der Überschreibung der Methode in einer prepend ed mixin. super in der Überschreibungsmethode verhält sich wie super o old in diesem Vorschlag.

12voto

Veger Punkte 35866

Schauen Sie sich das Aliasing von Methoden an, das ist eine Art Umbenennung der Methode in einen neuen Namen.

Weitere Informationen und einen Ausgangspunkt finden Sie hier Ersetzungsmethoden Artikel (insbesondere der erste Teil). Die Ruby-API-Dokumente liefert ebenfalls ein (weniger ausführliches) Beispiel.

-3voto

rplaurindo Punkte 1126

Die Klasse, die die Überschreibung vornimmt, muss nach der Klasse, die die ursprüngliche Methode enthält, neu geladen werden, also require in der Datei, die die Überschreibung vornimmt.

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