467 votes

Singe quand une méthode d’application de correctifs, vous pouvez appeler la méthode substituée de la nouvelle implémentation ?

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

1204voto

Jörg W Mittag Points 153275

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.

L'héritage

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.

Délégation

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.

Mixin Héritage (cassé)

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îne

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

Méthode D'Emballage

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. (UnboundMethods 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:


L'Avenir: Ruby 2.0

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é:

Méthode Combinators

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' bars'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'

13voto

Veger Points 17657

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

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.

Powered by:

X