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
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
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
.
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.