Dites que je suis en train de patcher une méthode dans une classe, comment pourrais-je appeler la méthode surchargée à partir de la méthode surchargée? C'est à dire. Quelque chose un peu comme super
Par exemple.
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
EDIT: Cela fait 9 ans que j'ai écrit cette réponse et cela mérite une intervention chirurgicale esthétique pour rester à jour.
Vous pouvez voir la dernière version avant l'édition ici .
Vous ne pouvez pas appeler la méthode écrasée par son nom ou son mot-clé. C’est l’une des nombreuses raisons pour lesquelles il est préférable d’éviter les correctifs avec un singe et de préférer l’héritage, puisque vous pouvez appeler la méthode overridden.
Donc, si possible, préférez quelque chose comme ceci:
_class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
_
Cela fonctionne si vous contrôlez la création des objets Foo
. Il suffit de changer chaque lieu qui crée un Foo
pour créer plutôt un ExtendedFoo
. Cela fonctionne encore mieux si vous utilisez Modèle de conception d'injection de dépendance , le Modèle de conception de méthode d'usine , le Modèle de conception de fabrique abstraite ou quelque chose du genre, car dans ce cas, il n’ya qu’un endroit où vous devez changer.
Si vous ne contrôlez pas la création des objets Foo
, par exemple parce qu’ils sont créés par une structure ne relevant pas de votre contrôle (comme Ruby-on-Rails pour exemple), vous pouvez utiliser le Wrapper Design Pattern :
_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'
_
Fondamentalement, à la limite du système, où l’objet Foo
entre dans votre code, vous l’enveloppez dans un autre objet, puis vous utilisez cet objet au lieu de l’objet original partout ailleurs dans votre code.
Ceci utilise la méthode d'assistance Object#DelegateClass
de la bibliothèque delegate
dans la bibliothèque stdlib.
Module#prepend
: Mixin en préparationLes deux méthodes ci-dessus nécessitent de changer de système pour éviter les correctifs monkey. Cette section présente la méthode préférée et la moins invasive de correction de singe si le changement de système n'était pas une option.
Module#prepend
a été ajouté pour prendre en charge plus ou moins exactement ce cas d'utilisation. _Module#prepend
_ fait la même chose que _Module#include
_, sauf qu'il mélange directement dans le mixin en dessous de la classe:
_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'
_
Remarque: j'ai également écrit un peu à propos de _Module#prepend
_ dans cette question: Ruby module prepend vs derivation
J'ai vu certaines personnes essayer (et demander pourquoi cela ne fonctionne pas ici sur StackOverflow) quelque chose comme ceci, c'est-à-dire include
ing un mixin au lieu de prepend
ing:
_class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
_
Malheureusement, ça ne marchera pas. C’est une bonne idée, car elle utilise l’héritage, ce qui signifie que vous pouvez utiliser super
. Cependant, Module#include
insère le mixin above la classe dans la hiérarchie de l'héritage, ce qui signifie que _FooExtensions#bar
_ ne sera jamais appelé (et si étaient appelés, le super
ne ferait pas réellement référence à _Foo#bar
_ mais plutôt à _Object#bar
_ qui n'existe pas), puisque _Foo#bar
_ sera toujours trouvé en premier.
La grande question est: comment pouvons-nous conserver la méthode bar
sans réellement conserver une méthode réelle? Comme souvent, la réponse réside dans la programmation fonctionnelle. Nous récupérons la méthode en tant que objet réel, et nous utilisons une fermeture (c'est-à-dire un bloc) pour nous assurer que nous et seulement nous tenons cet objet:
_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'
_
C'est très propre: puisque _old_bar
_ est juste une variable locale, elle sortira de la portée à la fin du corps de la classe et il est impossible d'y accéder de n'importe où, même en utilisant la réflexion! Et depuis Module#define_method
prend un bloc et ferme des blocs sur leur environnement lexical environnant (qui est pourquoi nous utilisons _define_method
_ au lieu de def
ici), it (et only it) aura toujours accès à _old_bar
_, même après sa sortie du champ d'application.
Brève explication:
_old_bar = instance_method(:bar)
_
Nous encapsulons ici la méthode bar
dans un objet de méthode UnboundMethod
et l’attribuons à la variable locale _old_bar
_. Cela signifie que nous avons maintenant un moyen de conserver bar
même après son écrasement.
_old_bar.bind(self)
_
C'est un peu délicat. Fondamentalement, dans Ruby (et dans à peu près tous les langages OO basés sur une distribution unique), une méthode est liée à un objet récepteur spécifique, appelé self
dans Ruby. En d'autres termes: une méthode sait toujours sur quel objet elle a été appelée, elle sait ce que sa self
est. Mais, nous avons récupéré la méthode directement à partir d’une classe. Comment sait-elle en quoi consiste son self
?
Eh bien, ce n’est pas le cas, c’est pourquoi nous devons bind
notre UnboundMethod
à un objet d’abord, ce qui renverra un Method
objet que nous pouvons ensuite appeler. (UnboundMethod
s ne peut pas être appelé, car ils ne savent pas quoi faire sans connaître leur self
.)
Et que faisons-nous bind
? Nous avons simplement bind
pour nous-mêmes, de cette façon, il se comportera exactement comme l'original bar
l'aurait!
Enfin, nous devons appeler le Method
renvoyé par bind
. Dans Ruby 1.9, il existe une nouvelle syntaxe astucieuse pour cela (.()
), mais si vous êtes sur la 1.8, vous pouvez simplement utiliser la méthode call
; c’est ce que .()
est traduit de toute façon.
Voici quelques autres questions, où certains de ces concepts sont expliqués:
alias_method
chaîneLe problème que nous rencontrons avec notre patch de singe est que, lorsque nous écrasons la méthode, celle-ci est partie et nous ne pouvons plus l'appeler. Faisons donc une copie de sauvegarde!
_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'
_
Le problème, c’est que nous avons maintenant pollué l’espace de noms avec une méthode _old_bar
_ superflue. Cette méthode apparaîtra dans notre documentation, elle apparaîtra dans la complétion de code dans nos IDE, elle apparaîtra pendant la réflexion. De plus, il peut toujours être appelé, mais on peut supposer que nous l'avons corrigé, parce que nous n'aimions pas son comportement au départ, de sorte que nous ne voudrions peut-être pas que d'autres personnes l'appellent.
Bien que cela présente des propriétés indésirables, il est malheureusement devenu populaire grâce à Module#alias_method_chain
de AciveSupport.
Dans le cas où vous n’auriez besoin que du comportement différent dans quelques emplacements spécifiques et non dans l’ensemble du système, vous pouvez utiliser Refinements pour limiter le patch monkey à une étendue spécifique. Je vais le démontrer ici en utilisant l'exemple _Module#prepend
_ ci-dessus:
_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!
_
Vous pouvez voir un exemple plus sophistiqué d'utilisation de Refinements dans cette question: Comment activer le correctif de singe pour une méthode spécifique?
Avant que la communauté Ruby ne se décide sur _Module#prepend
_, il y avait de nombreuses idées différentes que vous pourriez parfois voir référencées dans des discussions plus anciennes. Tous ces éléments sont assimilés à _Module#prepend
_.
Une idée a été l'idée des combinateurs de méthodes de CLOS. Il s’agit d’une version très légère d’un sous-ensemble de programmation orientée aspect.
Utiliser une syntaxe comme
_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
_
vous seriez en mesure de "vous accrocher" à l'exécution de la méthode bar
.
Toutefois, il n’est pas clair si et comment vous avez accès à la valeur de retour de bar
dans _bar:after
_. Peut-être pourrions-nous (ab) utiliser le mot clé super
?
_class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
_
Le combinateur before équivaut à prepend
ing un mixin avec une méthode prioritaire qui appelle super
à la même end de la méthode. De même, le combinateur after équivaut à prepend
ing un mixin avec une méthode de substitution qui appelle super
à la même début de la méthode.
Vous pouvez également créer des éléments avant et après avoir appelé super
, vous pouvez appeler super
plusieurs fois et récupérer et manipuler la valeur de retour de super
, ce qui rend prepend
plus puissant que les combinateurs de méthodes.
_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
_
et
_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
mot-cléCette idée ajoute un nouveau mot clé similaire à super
, qui vous permet d'appeler la méthode écrasée de la même manière que super
vous permet d'appeler la méthode overridden:
_class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
_
Le principal problème avec ceci est que c'est incompatible avec le passé: si vous avez une méthode appelée old
, vous ne pourrez plus l'appeler!
super
dans une méthode prioritaire dans un mixage prepend
ed est essentiellement identique à old
dans cette proposition.
redef
mot-cléSemblable à ce qui précède, mais au lieu d’ajouter un nouveau mot clé pour appeler la méthode écrasée et laisser def
seul, nous ajoutons un nouveau mot clé pour redefining. Ceci est rétrocompatible, car la syntaxe est illégale pour le moment:
_class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
_
Au lieu d’ajouter deux nouveaux mots clés, nous pourrions également redéfinir la signification de super
dans redef
:
_class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
_
redef
ining une méthode équivaut à redéfinir la méthode dans un mixin prepend
ed. super
dans la méthode de substitution se comporte comme super
ou old
dans cette proposition.
Jetez un coup d'œil aux méthodes d'aliasing, il s'agit en quelque sorte de renommer la méthode en un nouveau nom.
Pour plus d'informations et un point de départ, jetez un coup d'œil à ceci article sur les méthodes de remplacement (en particulier la première partie). Le documentation de l'API Ruby fournit également un exemple (moins élaboré).