Supposons que je corrige une méthode dans une classe, comment pourrais-je appeler la méthode substituée à partir de la méthode prioritaire? C'est à dire quelque chose un peu commesuper
Par exemple
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
la source
la source
Foo
et l'utilisation deFoo::bar
. Vous devez donc patcher la méthode.Réponses:
EDIT : Cela fait 9 ans que j'ai écrit cette réponse à l'origine, et cela mérite une chirurgie esthétique pour la garder à jour.
Vous pouvez voir la dernière version avant l'édition ici .
Vous ne pouvez pas appeler la méthode remplacée par nom ou mot-clé. C'est l'une des nombreuses raisons pour lesquelles le patch de singe doit être évité et l'héritage préféré à la place, car vous pouvez évidemment appeler la méthode substituée .
Éviter les patchs de singe
Héritage
Donc, si possible, vous devriez préférer quelque chose comme ça:
Cela fonctionne, si vous contrôlez la création des
Foo
objets. Il suffit de changer chaque endroit qui crée unFoo
pour en créer unExtendedFoo
. Cela fonctionne encore mieux si vous utilisez le modèle de conception d'injection de dépendance , le modèle de conception de méthode d'usine , le modèle de conception d'usine abstraite ou quelque chose du genre, car dans ce cas, il n'y a qu'un seul endroit que vous devez modifier.Délégation
Si vous ne contrôlez pas la création des
Foo
objets, par exemple parce qu'ils sont créés par un framework hors de votre contrôle (commerubis sur railspar exemple), vous pouvez alors utiliser le modèle de conception Wrapper :Fondamentalement, à la frontière du système, où l'
Foo
objet entre dans votre code, vous l'enveloppez dans un autre objet, puis utilisez cet objet au lieu de l'original partout ailleurs dans votre code.Cela utilise la
Object#DelegateClass
méthode d'assistance de ladelegate
bibliothèque dans le stdlib.Patch de singe «propre»
Module#prepend
: Mixin PrependingLes deux méthodes ci-dessus nécessitent de changer le système pour éviter les patches de singe. Cette section montre la méthode préférée et la moins invasive de correction de singe, si le changement du système n'est 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 queModule#include
, sauf qu'il se mélange dans le mixin juste en dessous de la classe:Remarque: J'ai également écrit un peu
Module#prepend
dans cette question: Ruby module prepend vs derivationHéritage Mixin (cassé)
J'ai vu certaines personnes essayer (et demander pourquoi cela ne fonctionne pas ici sur StackOverflow) quelque chose comme ça, c'est-à-dire
include
ing un mixin au lieu deprepend
le faire:Malheureusement, cela ne fonctionnera pas. C'est une bonne idée, car elle utilise l'héritage, ce qui signifie que vous pouvez l'utiliser
super
. Cependant,Module#include
insère le mixin au - dessus de la classe dans la hiérarchie d'héritage, ce qui signifie qu'ilFooExtensions#bar
ne sera jamais appelé (et s'il était appelé, lesuper
ne se réfèrerait pas réellement àFoo#bar
maisObject#bar
n'existe pas), car ilFoo#bar
sera toujours trouvé en premier.Emballage de méthode
La grande question est: comment pouvons-nous conserver la
bar
méthode, sans vraiment garder une méthode réelle ? La réponse réside, comme c'est souvent le cas, dans la programmation fonctionnelle. Nous saisissons la méthode en tant qu'objet réel , et nous utilisons une fermeture (c'est-à-dire un bloc) pour nous assurer que nous et seulement nous nous accrochons à cet objet:C'est très propre: puisqu'il ne
old_bar
s'agit que d'une variable locale, elle sera hors de portée à la fin du corps de classe, et il est impossible d'y accéder de n'importe où, même en utilisant la réflexion! Et puisqueModule#define_method
prend un bloc, et que les blocs se referment sur leur environnement lexical environnant (c'est pourquoi nous utilisons à ladefine_method
place d'def
ici), il (et seulement lui) y aura toujours accèsold_bar
, même après qu'il soit hors de portée.Brève explication:
Ici, nous enveloppons la
bar
méthode dans unUnboundMethod
objet de méthode et l'affectons à la variable localeold_bar
. Cela signifie que nous avons maintenant un moyen de conserverbar
même après qu'il a été remplacé.C'est un peu délicat. Fondamentalement, dans Ruby (et dans presque tous les langages OO à répartition unique), une méthode est liée à un objet récepteur spécifique, appelé
self
en Ruby. En d'autres termes: une méthode sait toujours à quel objet elle a été appelée, elle sait ce qu'elleself
est. Mais, nous avons saisi la méthode directement dans une classe, comment sait-elle ce queself
c'est?Eh bien, ce n'est pas le cas, c'est pourquoi nous devons d'abord
bind
accéderUnboundMethod
à un objet, qui renverra unMethod
objet que nous pourrons ensuite appeler. (LesUnboundMethod
appels ne peuvent pas être appelés, car ils ne savent pas quoi faire sans connaître leurself
.)Et que faisons-nous
bind
? Nous le faisons simplementbind
pour nous-mêmes, de cette façon, il se comportera exactement comme l'original l'bar
aurait fait!Enfin, nous devons appeler celui
Method
qui est retournébind
. Dans Ruby 1.9, il y a une nouvelle syntaxe astucieuse pour that (.()
), mais si vous êtes sur 1.8, vous pouvez simplement utiliser lacall
méthode; c'est ce qui.()
est traduit de toute façon.Voici quelques autres questions, où certains de ces concepts sont expliqués:
Patch de singe «sale»
alias_method
chaîneLe problème que nous rencontrons avec notre patch de singe est que lorsque nous remplaçons la méthode, la méthode disparaît, nous ne pouvons donc plus l'appeler. Alors, faisons juste une copie de sauvegarde!
Le problème avec cela est que nous avons maintenant pollué l'espace de noms avec une
old_bar
méthode superflue . Cette méthode apparaîtra dans notre documentation, elle apparaîtra dans l'achèvement du code dans nos IDE, elle apparaîtra pendant la réflexion. En outre, il peut toujours être appelé, mais on peut supposer que nous avons corrigé le singe, parce que nous n'aimions pas son comportement en premier lieu, donc nous pourrions ne pas vouloir que d'autres personnes l'appellent.Malgré le fait que cela ait des propriétés indésirables, il est malheureusement devenu populaire grâce à AciveSupport
Module#alias_method_chain
.Un aparté: raffinements
Dans le cas où vous n'avez besoin que du comportement différent dans quelques endroits spécifiques et non dans tout le système, vous pouvez utiliser des raffinements pour restreindre le patch singe à une portée spécifique. Je vais le démontrer ici en utilisant l'
Module#prepend
exemple ci-dessus:Vous pouvez voir un exemple plus sophistiqué d'utilisation des raffinements dans cette question: Comment activer le patch singe pour une méthode spécifique?
Idées abandonnées
Avant que la communauté Ruby ne s'installe
Module#prepend
, il y avait plusieurs idées différentes flottantes que vous pouvez parfois voir référencées dans des discussions plus anciennes. Tous ces éléments sont regroupés parModule#prepend
.Combinateurs de méthodes
Une idée était l'idée de combinateurs de méthodes de CLOS. Il s'agit essentiellement d'une version très légère d'un sous-ensemble de programmation orientée aspect.
Utiliser une syntaxe comme
vous pourriez «vous accrocher» à l'exécution de la
bar
méthode.Il n'est cependant pas tout à fait clair si et comment vous pouvez accéder à
bar
la valeur de retour debar:after
. Peut-être pourrions-nous (ab) utiliser lesuper
mot-clé?Remplacement
Le combinateur avant est équivalent à
prepend
un mixin avec une méthode prioritaire qui appellesuper
à la toute fin de la méthode. De même, le combinateur after est équivalent àprepend
un mixin avec une méthode prioritaire qui appellesuper
au tout début de la méthode.Vous pouvez également faire des choses avant et après l'appel
super
, vous pouvez appelersuper
plusieurs fois et récupérer et manipulersuper
la valeur de retour de, ce qui rendprepend
plus puissant que les combinateurs de méthodes.et
old
mot-cléCette idée ajoute un nouveau mot-clé similaire à
super
, qui vous permet d'appeler la méthode remplacée de la même manièresuper
vous permet d'appeler la méthode remplacée :Le principal problème avec cela est qu'il est incompatible en amont: si vous avez appelé la méthode
old
, vous ne pourrez plus l'appeler!Remplacement
super
dans une méthode prioritaire dans unprepend
mixage électronique est essentiellement le même queold
dans cette proposition.redef
mot-cléSemblable à ci-dessus, mais au lieu d'ajouter un nouveau mot clé pour appeler la méthode remplacée et de laisser
def
seul, nous ajoutons un nouveau mot clé pour redéfinir les méthodes. Ceci est rétrocompatible, car la syntaxe est actuellement illégale de toute façon:Au lieu d'ajouter deux nouveaux mots clés, nous pourrions également redéfinir la signification de l'
super
intérieurredef
:Remplacement
redef
ining une méthode équivaut à remplacer la méthode dans unprepend
mixage ed.super
dans la méthode prioritaire se comporte commesuper
ouold
dans cette proposition.la source
bind
la mêmeold_method
variable?UnboundMethod#bind
retournera un nouveau, différentMethod
, donc, je ne vois aucun conflit se produire, que vous l'appeliez deux fois de suite ou deux fois en même temps à partir de threads différents.old
etredef
? Mon 2.0.0 n'en a pas. Ah, il est difficile de ne pas manquer les autres idées concurrentes qui ne sont pasJetez un œil aux méthodes d'alias, c'est en quelque sorte renommer la méthode sous un nouveau nom.
Pour plus d'informations et un point de départ, consultez cet article sur les méthodes de remplacement (en particulier la première partie). La documentation de l'API Ruby fournit également un exemple (moins élaboré).
la source
La classe qui fera la substitution doit être rechargée après la classe qui contient la méthode d'origine, donc
require
dans le fichier qui effectuera la substitution.la source