Angenommen, ich bin ein Affe, der eine Methode in einer Klasse patcht. Wie kann ich die überschriebene Methode von der überschreibenden Methode aus aufrufen? Dh so etwas wiesuper
Z.B
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
quelle
quelle
Foo
und Verwendung von nicht steuernFoo::bar
. Sie müssen also die Methode mit einem Affen-Patch versehen .Antworten:
EDIT : Es ist 9 Jahre her, seit ich diese Antwort ursprünglich geschrieben habe, und es verdient eine Schönheitsoperation, um sie auf dem neuesten Stand zu halten.
Sie können die letzte Version vor der Bearbeitung hier sehen .
Sie können die überschriebene Methode nicht über Name oder Schlüsselwort aufrufen . Dies ist einer der vielen Gründe, warum das Patchen von Affen vermieden und stattdessen die Vererbung bevorzugt werden sollte, da Sie natürlich die überschriebene Methode aufrufen können .
Vermeiden von Affen-Patches
Erbe
Wenn möglich, sollten Sie so etwas bevorzugen:
Dies funktioniert, wenn Sie die Erstellung der
Foo
Objekte steuern . Ändern Sie einfach jeden Ort, der ein erstellt,Foo
um stattdessen ein zu erstellenExtendedFoo
. Dies funktioniert sogar noch besser, wenn Sie das Entwurfsmuster für Abhängigkeitsinjektionen , das Entwurfsmuster für Factory-Methoden , das Entwurfsmuster für abstrakte Fabriken oder ähnliches verwenden, da in diesem Fall nur der Ort geändert werden muss.Delegation
Wenn Sie die Erstellung der
Foo
Objekte nicht steuern , z. B. weil sie von einem Framework erstellt werden, das außerhalb Ihrer Kontrolle liegt (zRuby-on-RailsZum Beispiel), dann könnten Sie das Wrapper Design Pattern verwenden :Grundsätzlich
Foo
wickeln Sie das Objekt an der Grenze des Systems, an der es in Ihren Code eingeht, in ein anderes Objekt ein und verwenden dieses Objekt dann anstelle des ursprünglichen Objekts überall in Ihrem Code.Dies verwendet die Hilfsmethode
Object#DelegateClass
aus derdelegate
Bibliothek in der stdlib."Clean" Monkey Patching
Module#prepend
: Mixin PrependingBei den beiden oben genannten Methoden muss das System geändert werden, um das Patchen von Affen zu vermeiden. Dieser Abschnitt zeigt die bevorzugte und am wenigsten invasive Methode zum Patchen von Affen, falls ein Systemwechsel nicht möglich ist.
Module#prepend
wurde hinzugefügt, um mehr oder weniger genau diesen Anwendungsfall zu unterstützen.Module#prepend
macht das Gleiche wieModule#include
, außer dass es sich im Mixin direkt unter der Klasse mischt :Hinweis: Ich habe
Module#prepend
in dieser Frage auch ein wenig darüber geschrieben : Ruby-Modul vorangestellt gegen AbleitungMixin-Vererbung (gebrochen)
Ich habe einige Leute gesehen, die versucht haben (und gefragt haben, warum es hier bei StackOverflow nicht funktioniert), so etwas, dh
include
ein Mixin anstattprepend
es zu machen:Das wird leider nicht funktionieren. Es ist eine gute Idee, weil es Vererbung verwendet, was bedeutet, dass Sie verwenden können
super
. AllerdingsModule#include
fügt die mixin über die Klasse in der Vererbungshierarchie, die Mittel , dieFooExtensions#bar
nie aufgerufen werden (und wenn es wurden genannt, diesuper
nicht tatsächlich beziehen sich auf ,Foo#bar
sondern aufObject#bar
die es nicht gibt), daFoo#bar
immer zuerst gefunden werden.Methodenverpackung
Die große Frage ist: Wie können wir an der
bar
Methode festhalten , ohne tatsächlich an einer tatsächlichen Methode festzuhalten ? Die Antwort liegt wie so oft in der funktionalen Programmierung. Wir erhalten die Methode als tatsächliches Objekt und verwenden einen Abschluss (dh einen Block), um sicherzustellen, dass wir und nur wir an diesem Objekt festhalten:Dies ist sehr sauber: Da
old_bar
es sich nur um eine lokale Variable handelt, wird sie am Ende des Klassenkörpers nicht mehr gültig sein, und es ist unmöglich, von überall darauf zuzugreifen, selbst wenn Reflektion verwendet wird! Und daModule#define_method
ein Block benötigt wird und Blöcke über der umgebenden lexikalischen Umgebung geschlossen werden ( weshalb wirdefine_method
stattdef
hier verwenden), hat er (und nur er) weiterhin Zugriff daraufold_bar
, selbst nachdem er den Gültigkeitsbereich verlassen hat.Kurze Erklärung:
Hier verpacken wir die
bar
Methode in einUnboundMethod
Methodenobjekt und weisen es der lokalen Variablen zuold_bar
. Das heißt, wir haben jetzt die Möglichkeit,bar
auch nach dem Überschreiben daran festzuhalten .Das ist etwas knifflig. Grundsätzlich ist in Ruby (und in nahezu allen Single-Dispatch-basierten OO-Sprachen) eine Methode an ein bestimmtes Empfängerobjekt gebunden, das
self
in Ruby aufgerufen wird . Mit anderen Worten: Eine Methode weiß immer, auf welchem Objekt sie aufgerufen wurde, sie weiß, was esself
ist. Aber wir haben die Methode direkt aus einer Klasse geholt. Woher weiß sie, was sieself
ist?Nun, es funktioniert nicht, weshalb wir brauchen
bind
unsereUnboundMethod
auf ein Objekt zuerst, das ein zurückkehrenMethod
Objekt , das wir dann anrufen. (UnboundMethod
s können nicht angerufen werden, weil sie nicht wissen, was sie tun sollen, ohne ihre zu kennenself
.)Und wozu machen wir
bind
das? Wir haben es einfachbind
für uns, so wird es sich genau so verhalten wie das Originalbar
!Zuletzt müssen wir das zurückrufen, von dem
Method
zurückgegeben wirdbind
. In Ruby 1.9 gibt es dafür eine raffinierte neue Syntax (.()
), aber wenn Sie 1.8 verwenden, können Sie einfach diecall
Methode verwenden. das.()
wird sowieso übersetzt.Hier sind einige andere Fragen, in denen einige dieser Konzepte erläutert werden:
"Dirty" Monkey Patching
alias_method
KetteDas Problem, das wir mit dem Patchen von Affen haben, ist, dass die Methode beim Überschreiben der Methode weg ist und wir sie nicht mehr aufrufen können. Machen wir also einfach eine Sicherungskopie!
Das Problem dabei ist, dass wir den Namespace jetzt mit einer überflüssigen
old_bar
Methode verschmutzt haben . Diese Methode wird in unserer Dokumentation angezeigt, sie wird bei der Code-Vervollständigung in unseren IDEs angezeigt und wird während der Reflexion angezeigt. Es kann auch immer noch aufgerufen werden, aber vermutlich haben wir es mit Affen gepatcht, weil uns sein Verhalten an erster Stelle nicht gefallen hat, sodass wir möglicherweise nicht möchten, dass andere Leute es anrufen.Trotz der Tatsache, dass dies einige unerwünschte Eigenschaften hat, ist es leider durch AciveSupport's populär geworden
Module#alias_method_chain
.Nebenbei: Verfeinerungen
Falls Sie das unterschiedliche Verhalten nur an bestimmten Stellen und nicht im gesamten System benötigen, können Sie mithilfe von Verfeinerungen den Affen-Patch auf einen bestimmten Bereich beschränken. Ich werde es hier anhand des
Module#prepend
obigen Beispiels demonstrieren :In dieser Frage sehen Sie ein komplexeres Beispiel für die Verwendung von Verfeinerungen: Wie aktiviere ich den Affen-Patch für eine bestimmte Methode?
Verlassene Ideen
Bevor sich die Ruby-Community niederließ
Module#prepend
, gab es mehrere verschiedene Ideen, auf die gelegentlich in älteren Diskussionen verwiesen wird. All dies wird von zusammengefasstModule#prepend
.Methodenkombinatoren
Eine Idee war die Idee von Methodenkombinatoren von CLOS. Dies ist im Grunde eine sehr leichte Version einer Teilmenge der aspektorientierten Programmierung.
Mit Syntax wie
Sie könnten sich in die Ausführung der
bar
Methode "einhaken" .Es ist jedoch nicht ganz klar, ob und wie Sie Zugriff auf
bar
den Rückgabewert von erhaltenbar:after
. Vielleicht könnten wir dassuper
Schlüsselwort (ab) verwenden ?Ersatz
Der Vorher-Kombinator entspricht
prepend
einem Mixin mit einer überschreibenden Methode,super
die ganz am Ende der Methode aufgerufen wird. Ebenso entspricht der After-Combinatorprepend
einem Mixin mit einer überschreibenden Methode,super
die ganz am Anfang der Methode aufgerufen wird.Sie können auch Dinge vor und nach dem Aufruf erledigen
super
, Sie könnensuper
mehrmals aufrufen undsuper
den Rückgabewert sowohl abrufen als auch bearbeiten , wasprepend
leistungsfähiger ist als Methodenkombinatoren.und
old
StichwortDiese Idee fügt ein neues Schlüsselwort hinzu
super
, mit dem Sie die überschriebene Methode auf dieselbe Weisesuper
aufrufen können, mit der Sie die überschriebene Methode aufrufen können :Das Hauptproblem dabei ist, dass es abwärtskompatibel ist: Wenn Sie eine Methode aufgerufen haben
old
, können Sie sie nicht mehr aufrufen!Ersatz
super
in einer übergeordneten Methode in einemprepend
ed mixin ist im Wesentlichen das gleiche wieold
in diesem Vorschlag.redef
StichwortÄhnlich wie oben, aber anstatt ein neues Schlüsselwort zum Aufrufen der überschriebenen Methode
def
hinzuzufügen und in Ruhe zu lassen, fügen wir ein neues Schlüsselwort zum Neudefinieren von Methoden hinzu. Dies ist abwärtskompatibel, da die Syntax derzeit ohnehin unzulässig ist:Anstatt zwei neue Schlüsselwörter hinzuzufügen , könnten wir auch die Bedeutung von
super
inside neu definierenredef
:Ersatz
redef
Das Einfügen einer Methode entspricht dem Überschreiben der Methode in einemprepend
ed-Mixin.super
in der überschreibenden Methode verhält sich wiesuper
oderold
in diesem Vorschlag.quelle
bind
dieselbeold_method
Variable aufrufen ?UnboundMethod#bind
ein neuer, anderer Aufruf zurückgegebenMethod
, sodass kein Konflikt auftritt, unabhängig davon, ob Sie ihn zweimal hintereinander oder zweimal gleichzeitig aus verschiedenen Threads aufrufen.old
undredef
? Mein 2.0.0 hat sie nicht. Ah, es ist schwer, die anderen konkurrierenden Ideen, die es nicht in Ruby schafften,Schauen Sie sich Aliasing-Methoden an. Auf diese Weise wird die Methode in einen neuen Namen umbenannt.
Weitere Informationen und einen Ausgangspunkt finden Sie in diesem Artikel über Ersetzungsmethoden (insbesondere im ersten Teil). Die Ruby-API-Dokumente bieten auch ein (weniger ausführliches) Beispiel.
quelle
Die Klasse, die überschrieben wird, muss nach der Klasse, die die ursprüngliche Methode enthält, neu geladen werden, damit
require
sie in der Datei überschrieben wird.quelle