Supponiamo che io stia eseguendo il patching di un metodo in una class, come potrei chiamare il metodo sottoposto a override dal metodo di override? Vale a dire qualcosa di super
Per esempio
class Foo def bar() "Hello" end end class Foo def bar() super() + " World" end end >> Foo.new.bar == "Hello World"
EDIT : Sono passati 5 anni da quando ho originariamente scritto questa risposta, e merita un intervento di chirurgia estetica per tenerlo aggiornato.
Puoi vedere l’ultima versione prima della modifica qui .
Non è ansible chiamare il metodo sovrascritto per nome o parola chiave. Questo è uno dei tanti motivi per cui si dovrebbe evitare l’uso di patch per scimmie e preferibilmente preferire l’ereditarietà, poiché ovviamente è ansible chiamare il metodo sottoposto a override .
Quindi, se ansible, dovresti preferire qualcosa del genere:
class Foo def bar 'Hello' end end class ExtendedFoo < Foo def bar super + ' World' end end ExtendedFoo.new.bar # => 'Hello World'
Questo funziona, se controlli la creazione degli oggetti Foo
. Basta cambiare ogni posto che crea un Foo
per creare invece un ExtendedFoo
. Funziona ancora meglio se si utilizza il modello di progettazione dell’iniezione di dipendenza , il modello di progettazione del metodo di fabbrica , il modello di progettazione di una fabbrica astratta o qualcosa del genere, poiché in tal caso, vi è solo un luogo che è necessario modificare.
Se non controlli la creazione degli oggetti Foo
, ad esempio perché sono creati da un framework che è al di fuori del tuo controllo (ad esempio ruby-on-rails, ad esempio), puoi utilizzare il modello di design Wrapper :
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'
Fondamentalmente, al confine del sistema, dove l’object Foo
entra nel tuo codice, lo avvolgi in un altro object, e quindi usi quell’object al posto di quello originale ovunque nel tuo codice.
Utilizza il metodo helper Object#DelegateClass
dalla libreria delegate
nello stdlib.
Module#prepend
anteporre: Pre-miscelazione I due metodi sopra richiedono la modifica del sistema per evitare il patch delle scimmie. Questa sezione mostra il metodo preferito e meno invasivo di patch per scimmie, dovrebbe cambiare il sistema non essere un’opzione.
Module#prepend
stato aggiunto per supportare più o meno esattamente questo caso d’uso. Module#prepend
fa la stessa cosa del Module#include
, eccetto che si mescola nel mixin direttamente sotto la class:
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'
Nota: ho anche scritto un po ‘su Module#prepend
in questa domanda: il modulo Ruby precede rispetto alla derivazione
Ho visto alcune persone provare (e chiedere sul perché non funziona qui su StackOverflow) qualcosa di simile, cioè include
un mixin invece di prepend
:
class Foo def bar 'Hello' end end module FooExtensions def bar super + ' World' end end class Foo include FooExtensions end
Sfortunatamente, questo non funzionerà. È una buona idea, perché usa l’ereditarietà, il che significa che puoi usare super
. Tuttavia, il Module#include
inserimento del mixin sopra la class nella gerarchia dell’ereditarietà, il che significa che la FooExtensions#bar
non verrà mai chiamata (e se fosse chiamata, il super
non si riferirebbe alla Foo#bar
di Foo#bar
ma piuttosto alla Object#bar
che non esiste), poiché la Foo#bar
verrà sempre trovata per prima.
La grande domanda è: come possiamo aggrapparci al metodo della bar
, senza effettivamente mantenere un metodo reale ? La risposta sta, come spesso accade, nella programmazione funzionale. Otteniamo il metodo come object reale e usiamo una chiusura (cioè un blocco) per assicurarci che noi e solo ci aggrappiamo a quell’object:
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'
Questo è molto pulito: poiché old_bar
è solo una variabile locale, andrà al di fuori dell’ambito alla fine del corpo della class, ed è imansible accedervi da qualsiasi luogo, anche usando la reflection! E poiché Module#define_method
prende un blocco e blocca vicino al loro ambiente lessicale circostante (che è il motivo per cui stiamo usando define_method
invece di def
qui), esso (e solo esso) avrà ancora accesso a old_bar
, anche dopo che è uscito di scopo.
Breve spiegazione:
old_bar = instance_method(:bar)
Qui stiamo avvolgendo il metodo bar
in un object metodo UnboundMethod
e assegnandolo alla variabile locale old_bar
. Ciò significa che ora abbiamo un modo per mantenere la bar
anche dopo che è stata sovrascritta.
old_bar.bind(self)
Questo è un po ‘complicato. Fondamentalmente, in Ruby (e praticamente in tutti i linguaggi OO basati sulla singola spedizione), un metodo è associato a un object ricevitore specifico, chiamato self
in Ruby. In altre parole: un metodo sa sempre a quale object è stato chiamato, sa qual è il suo self
. Ma, abbiamo afferrato il metodo direttamente da una class, come fa a sapere di cosa self
tratta?
Bene, non lo è, ed è per questo che abbiamo bisogno di bind
nostro UnboundMethod
a un object, che restituirà un object Method
che possiamo quindi chiamare. ( UnboundMethod
s non può essere chiamato, perché non sanno cosa fare senza conoscere se self
.)
E a cosa lo bind
? Lo bind
semplicemente a noi stessi, in questo modo si comporterà esattamente come avrebbe fatto la bar
originale!
Infine, dobbiamo chiamare il Method
restituito da bind
. In Ruby 1.9, c’è una bella syntax nuova per quello ( .()
), Ma se si è su 1.8, si può semplicemente usare il metodo call
; è quello che .()
viene tradotto in ogni caso.
Ecco un paio di altre domande, in cui vengono spiegati alcuni di questi concetti:
alias_method
Il problema che stiamo riscontrando con la nostra patch di scimmia è che quando sovrascriviamo il metodo, il metodo è scomparso, quindi non possiamo più chiamarlo. Quindi, facciamo una copia di backup!
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'
Il problema è che ora abbiamo inquinato lo spazio dei nomi con un metodo superfluo old_bar
. Questo metodo apparirà nella nostra documentazione, verrà mostrato in codice nei nostri IDE, verrà mostrato durante la riflessione. Inoltre, può ancora essere chiamato, ma presumibilmente lo abbiamo modificato, perché non ci piaceva il suo comportamento in primo luogo, quindi potremmo non volere che altre persone lo chiamino.
Nonostante il fatto che questo abbia alcune proprietà indesiderate, purtroppo è diventato popolare attraverso il Module#alias_method_chain
.
Nel caso in cui sia necessario il comportamento differente in alcuni punti specifici e non nell’intero sistema, è ansible utilizzare i perfezionamenti per limitare la patch della scimmia a un ambito specifico. Ho intenzione di dimostrarlo qui usando l’esempio di Module#prepend
dall’alto:
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!
Puoi vedere un esempio più sofisticato di utilizzo dei Rifinimenti in questa domanda: Come abilitare la patch scimmia per un metodo specifico?
Prima che la community di Ruby si fosse stabilita sul Module#prepend
, c’erano molte idee diverse che potevano essere viste di tanto in tanto nelle discussioni precedenti. Tutti questi sono riassunti dal Module#prepend
.
Un’idea è stata l’idea di combinatori di metodi di CLOS. Questa è fondamentalmente una versione molto leggera di un sottoinsieme di Programmazione orientata agli aspetti.
Usando la syntax come
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
Sareste in grado di “agganciare” l’esecuzione del metodo della bar
.
Non è tuttavia abbastanza chiaro se e come si ottiene l’accesso al valore di ritorno della bar:after
all’interno della bar:after
. Forse potremmo (ab) usare la parola chiave super
?
class Foo def bar 'Hello' end end class Foo def bar:after super + ' World' end end
Il combinatore precedente equivale a far prepend
un mixin con un metodo prevalente che chiama super
alla fine del metodo. Allo stesso modo, il dopo combinatore equivale a prepend
un mixin con un metodo prevalente che chiama super
all’inizio del metodo.
Puoi anche fare cose prima e dopo aver chiamato super
, puoi chiamare super
volte più volte, ed entrambi recuperare e manipolare il valore di ritorno di super
, rendendo l’ prepend
più potente dei combinatori di metodi.
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
e
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
parola chiave Questa idea aggiunge una nuova parola chiave simile a super
, che consente di chiamare il metodo sovrascritto allo stesso modo in cui super
consente di chiamare il metodo sottoposto a override :
class Foo def bar 'Hello' end end class Foo def bar old + ' World' end end Foo.new.bar # => 'Hello World'
Il problema principale di questo è che è incompatibile con le versioni precedenti: se hai un metodo chiamato old
, non sarai più in grado di chiamarlo!
super
in un metodo prevalente in un prepend
è essenzialmente uguale a quello old
in questa proposta.
redef
parola chiave Simile a sopra, ma invece di aggiungere una nuova parola chiave per chiamare il metodo sovrascritto e lasciare solo def
, aggiungiamo una nuova parola chiave per ridefinire i metodi. Questo è retrocompatibile, dal momento che la syntax al momento è illegale comunque:
class Foo def bar 'Hello' end end class Foo redef bar old + ' World' end end Foo.new.bar # => 'Hello World'
Invece di aggiungere due nuove parole chiave, potremmo anche ridefinire il significato di super
all’interno di redef
:
class Foo def bar 'Hello' end end class Foo redef bar super + ' World' end end Foo.new.bar # => 'Hello World'
redef
un metodo equivale a sovrascrivere il metodo in un prepend
. super
nel metodo di sovrascrittura si comporta come super
o old
in questa proposta.
Dai un’occhiata ai metodi di aliasing, questo è un po ‘come rinominare il metodo con un nuovo nome.
Per ulteriori informazioni e un punto di partenza, dai un’occhiata a questo articolo sui metodi di sostituzione (in particolare la prima parte). I documenti API di Ruby forniscono anche (un esempio meno elaborato).
La class che eseguirà l’override deve essere ricaricata dopo la class che contiene il metodo originale, quindi require
nel file che farà overrride.