Dire que je suis singe patcher une méthode dans une classe, comment pourrais j’appeler la méthode substituée de la méthode de substitution ? C'est-à-dire quelque chose comme un peu``
Par exemple
Dire que je suis singe patcher une méthode dans une classe, comment pourrais j’appeler la méthode substituée de la méthode de substitution ? C'est-à-dire quelque chose comme un peu``
Par exemple
Vous ne pouvez pas appeler à l' écrasement de la méthode par nom ou par mot clé. C'est l'une des nombreuses raisons pour lesquelles monkey patching doit être évitée et l'héritage sera préféré au lieu de cela, puisque de toute évidence, vous pouvez appeler le substituée méthode.
Donc, si possible, vous devriez préférer 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 avez le contrôle de la création de l' Foo
objets. Il suffit de changer chaque lieu qui crée un Foo
au lieu de créer un ExtendedFoo
. Cela fonctionne encore mieux si vous utilisez l'Injection de Dépendance Modèle de Conception, la Méthode de Motif de Conception, l'Usine Modèle de Conception ou quelque chose le long de ces lignes.
Si vous n'avez pas le contrôle de la création de l' Foo
objets, par exemple parce qu'ils sont créés par un cadre qui est en dehors de votre contrôle (comme Ruby on Rails par exemple), alors vous pourriez utiliser le Wrapper Modèle de Conception:
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'
En gros, à la limite du système, où l' Foo
objet vient dans votre code, vous l'envelopper dans un autre objet, puis utilisez que de l'objet au lieu de l'original un peu partout dans votre code.
Vous pourriez être tenté de faire quelque chose comme ceci:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
Malheureusement, cela ne marchera pas. C'est une bonne idée, car il utilise l'héritage, ce qui signifie que vous pouvez utiliser super
. Toutefois, include
insère le mixin au-dessus de la classe dans la hiérarchie d'héritage, ce qui signifie qu' FooExtensions#bar
ne sera jamais appelé (et si c' étaient des appelés, l' super
ne serait pas en réalité Foo#bar
mais plutôt à l' Object#bar
qui n'existe pas), depuis Foo#bar
trouve toujours la première.
alias_method
chaîneLe problème que nous avons avec nos monkey patching est que quand on remplacer la méthode, la méthode est allé, de sorte que nous ne l'appelons plus. Donc, nous allons juste faire 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 avec cela est que nous avons maintenant pollué l'espace de noms avec un superflu old_bar
méthode. Cette méthode va se manifester dans notre documentation, il s'affichera dans la complétion de code dans nos IDEs, il apparaîtra au cours de la réflexion. Aussi, il peut encore être appelé, mais je suppose qu'il singe patché, parce que nous n'avons pas aimé son comportement en premier lieu, si nous ne voulons pas d'autres personnes à appeler.
Ainsi, la grande question est: comment pouvons-nous tenir à l' bar
méthode, sans réellement conserver autour d'une méthode? La réponse se trouve, comme il le fait souvent, dans la programmation fonctionnelle. Nous obtenir une prise de la méthode comme un objet, et nous utilisons un dispositif de fermeture (c'est à dire un bloc) pour s'assurer que nous et seulement nous maintenir sur 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'
Des méthodes que l'utilisation réelle de monkey patching au lieu de l'héritage ou de la délégation, c'est le plus propre: depuis old_bar
est juste une variable locale, il sera hors de portée, à la fin du corps de la classe, et il est impossible d'y accéder à partir de n'importe où, même à l'aide de la réflexion! Et depuis define_method
prend un bloc, et des blocs de fermer leur environnement environnement lexical (qui est pourquoi nous utilisons define_method
au lieu de def
ici), il (et seulement elle) auront toujours accès à l' old_bar
, même après qu'il a disparu hors de portée.
Petite explication:
old_bar = instance_method(:bar)
Ici, nous intégrons l' bar
méthode en UnboundMethod
méthode de l'objet et de l'affecter à la variable locale old_bar
. Cela signifie que, nous avons maintenant une façon de conserver bar
même après qu'il a été écrasé.
old_bar.bind(self)
C'est un peu délicat. Fondamentalement, en Ruby (et dans à peu près tous une seule expédition basée OO langues), une méthode est liée à un récepteur spécifique de l'objet, appelé self
en Ruby. En d'autres termes: une méthode sait toujours ce que l'objet qu'il a été appelé, il sait que son self
. Mais, nous avons pris la méthode directement à partir d'une classe, comment sait-il quel est son self
?
Eh bien, ça ne marche pas, c'est pourquoi nous devons bind
notre UnboundMethod
à un objet le premier, qui sera de retour une Method
objet que l'on peut alors appeler. (UnboundMethod
s ne peut pas être appelé, parce qu'ils ne savent pas quoi faire sans connaître leur self
.)
Et que faisons-nous bind
? Nous avons simplement bind
à nous-mêmes, de cette façon, il va se comporter exactement comme l'original bar
aurait!
Enfin, nous devons appeler l' Method
qui est retourné à partir de bind
. En Ruby 1.9, il y a quelques chouettes nouvelle syntaxe pour qu' (.()
), mais si vous êtes sur 1.8, vous pouvez simplement utiliser l' call
méthode; c'est ce qu' .()
se traduit de toute façon.
Ici sont un couple d'autres questions, où certains de ces concepts sont expliqués:
En Ruby 2.0, il y a un moyen de rendre cela plus facile.
Module#prepend
: Mixin Héritage (fixe)J'aime bien, parce qu'il utilise l'héritage, sans la nécessité pour les nouveaux mots-clés, la méthode combinators, nouvelle sémantique ou quelque chose comme ça.
Rappelez-vous ci-dessus dans le Mixin Héritage (cassé) , quand on nous dit que le problème est que le mixin est insérée au-dessus de la classe dans la hiérarchie d'héritage? Eh bien, Module#prepend
est tout simplement une nouvelle méthode qui fait la même chose que Module#include
, à l'exception de ce mélange dans le module directement en dessous de la classe:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions # the only change to above: prepend instead of include
end
Foo.new.bar # => 'Hello World'
Ce code fonctionne réellement, si vous obtenez une récente caisse du tronc, branche de la YARV code source et le compiler.
D'autres idées concurrentes qui n'ont pas été en Ruby ont été:
Une idée est l'idée de la méthode combinators de CLOS. Il s'agit d'un très léger version d'un sous-ensemble de la Programmation Orientée Aspects.
En utilisant la 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 serait capable de "crochet dans" l'exécution de l' bar
méthode.
Il n'est cependant pas tout à fait clair si et comment vous obtenez l'accès à l' bar
s'valeur de retour dans bar:after
. On pourrait peut-être (ab)utiliser l' super
mot-clé?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
old
mot-cléCette idée d'ajouter un nouveau mot-clé similaires à super
, ce qui vous permet de téléphoner à l' écrasement de la méthode de la même manière, super
vous permet d'appeler le substituée méthode:
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Le principal problème, c'est qu'il est rétrocompatible: si vous avez une méthode appelée old
, vous ne pourrez plus l'appeler!
redef
mot-cléComme ci-dessus, mais au lieu d'ajouter un nouveau mot-clé pour l'appel de la méthode écrasée et de quitter def
seulement, nous avons ajouter un nouveau mot-clé pour la redéfinition des méthodes. C'est rétro-compatible, car la syntaxe est actuellement illégal de toute façon:
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 avons pu également redéfinir le sens de l' super
à l'intérieur d' redef
:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
Jetez un oeil aux méthodes d’aliasing, c’est genre de renommer la méthode sous un nouveau nom.
Pour plus d’informations et un point de départ, jetez un oeil à cet article de méthodes de remplacement (surtout la première partie). La documentation de l’API Ruby, fournit également (un moins élaborées) exemple.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.