Digamos que soy mono parcheando un método en una clase, ¿cómo podría llamar al método anulado desde el método anulado? Es decir, algo parecidosuper
P.ej
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
fuente
fuente

Fooy el uso deFoo::bar. Así que tienes que parchear el método.Respuestas:
EDITAR : Han pasado 9 años desde que escribí originalmente esta respuesta, y merece una cirugía estética para mantenerla actualizada.
Puedes ver la última versión antes de la edición aquí .
No puede llamar al método sobrescrito por nombre o palabra clave. Esa es una de las muchas razones por las que se deben evitar los parches de mono y preferir la herencia, ya que obviamente se puede llamar al método anulado .
Evitar parches de mono
Herencia
Entonces, si es posible, debe preferir algo como esto:
Esto funciona si controlas la creación de los
Fooobjetos. Simplemente cambie cada lugar que crea unFoopara crear unExtendedFoo. Esto funciona incluso mejor si se utiliza la dependencia del diseño del modelo de inyección , el patrón Factory Method diseño , el patrón Resumen de diseño de fábrica o algo por el estilo, porque en ese caso, sólo hay lugar que necesita para el cambio.Delegación
Si no controla la creación de los
Fooobjetos, por ejemplo, porque son creados por un marco que está fuera de su control (comoRuby on Railspor ejemplo), entonces podría usar el Patrón de diseño de envoltura :Básicamente, en el límite del sistema, donde el
Fooobjeto entra en su código, lo envuelve en otro objeto y luego usa ese objeto en lugar del original en cualquier otro lugar de su código.Esto utiliza el
Object#DelegateClassmétodo auxiliar de ladelegatebiblioteca en stdlib.Parches de mono "limpios"
Module#prepend: Mezcla previaLos dos métodos anteriores requieren cambiar el sistema para evitar parches de mono. Esta sección muestra el método preferido y menos invasivo para el parche de monos, en caso de que cambiar el sistema no sea una opción.
Module#prependfue agregado para soportar más o menos exactamente este caso de uso.Module#prependhace lo mismo queModule#include, excepto que se mezcla en el mixin directamente debajo de la clase:Nota: También escribí un poco sobre
Module#prependesta pregunta: el módulo Ruby antepuesto vs derivaciónHerencia Mixin (rota)
He visto a algunas personas probar (y preguntar por qué no funciona aquí en StackOverflow) algo como esto, es decir
include, mezclar en lugar deprependhacerlo:Desafortunadamente, eso no funcionará. Es una buena idea, porque usa herencia, lo que significa que puedes usarla
super. Sin embargo,Module#includeinserta el mixin por encima de la clase en la jerarquía de herencia, lo que significa queFooExtensions#barnunca será llamado (y si se llama, elsuperno habría en realidad se refieren aFoo#bar, sino más bien aObject#barque no existe), ya queFoo#barsiempre se encontrará en primer lugar.Método de envoltura
La gran pregunta es: ¿cómo podemos aferrarnos al
barmétodo, sin realmente mantener un método real ? La respuesta radica, como suele suceder, en la programación funcional. Obtenemos el método como un objeto real , y usamos un cierre (es decir, un bloque) para asegurarnos de que nosotros y solo nosotros nos aferremos a ese objeto:Esto está muy limpio: como
old_bares solo una variable local, se saldrá del alcance al final del cuerpo de la clase, y es imposible acceder a él desde cualquier lugar, ¡ incluso usando la reflexión! Y dado queModule#define_methodtoma un bloque y los bloques se cierran sobre su entorno léxico circundante ( por eso lo estamos usando endefine_methodlugar dedefaquí), él (y solo él) todavía tendrá accesoold_bar, incluso después de que haya salido del alcance.Breve explicación:
Aquí estamos envolviendo el
barmétodo en unUnboundMethodobjeto de método y asignándolo a la variable localold_bar. Esto significa que ahora tenemos una forma de conservarlobarincluso después de que se haya sobrescrito.Esto es un poco complicado. Básicamente, en Ruby (y en casi todos los lenguajes OO basados en un solo despacho), un método está vinculado a un objeto receptor específico, llamado
selfen Ruby. En otras palabras: un método siempre sabe en qué objeto fue llamado, sabe cuálselfes. Pero, tomamos el método directamente de una clase, ¿cómo sabe quéselfes?Bueno, no es así, por eso necesitamos primero
bindnuestroUnboundMethodobjeto, que devolverá unMethodobjeto que luego podamos llamar. (UnboundMethodNo se pueden llamar s, porque no saben qué hacer sin saber suself).¿Y para qué lo hacemos
bind? Simplementebindlo hacemos para nosotros, de esa manera se comportará exactamente como lobarhubiera hecho el original .Por último, debemos llamar al
Methodque se devuelvebind. En Ruby 1.9, hay una nueva sintaxis ingeniosa para eso (.()), pero si está en 1.8, simplemente puede usar elcallmétodo; a eso es a lo que.()se traduce de todos modos.Aquí hay un par de otras preguntas, donde se explican algunos de esos conceptos:
Parches de mono "sucio"
alias_methodcadenaEl problema que tenemos con nuestro parche de mono es que cuando sobrescribimos el método, el método desaparece, por lo que ya no podemos llamarlo. Entonces, ¡hagamos una copia de seguridad!
El problema con esto es que ahora hemos contaminado el espacio de nombres con un
old_barmétodo superfluo . Este método aparecerá en nuestra documentación, se mostrará al completar el código en nuestros IDEs, se mostrará durante la reflexión. Además, todavía se puede llamar, pero presumiblemente lo hemos parcheado, porque no nos gustó su comportamiento en primer lugar, por lo que es posible que no queramos que otras personas lo llamen.A pesar de que tiene algunas propiedades indeseables, desafortunadamente se popularizó a través de AciveSupport's
Module#alias_method_chain.Un aparte: refinamientos
En caso de que solo necesite el comportamiento diferente en algunos lugares específicos y no en todo el sistema, puede usar Refinamientos para restringir el parche de mono a un alcance específico. Voy a demostrarlo aquí usando el
Module#prependejemplo de arriba:Puede ver un ejemplo más sofisticado del uso de Refinamientos en esta pregunta: ¿Cómo habilitar el parche de mono para un método específico?
Ideas abandonadas
Antes de que la comunidad de Ruby se estableciera
Module#prepend, había varias ideas diferentes flotando alrededor que ocasionalmente puede ver referenciadas en discusiones anteriores. Todos estos están subsumidos porModule#prepend.Método Combinadores
Una idea fue la idea de los combinadores de métodos de CLOS. Esta es básicamente una versión muy ligera de un subconjunto de Programación Orientada a Aspectos.
Usando sintaxis como
usted podría "engancharse" a la ejecución del
barmétodo.Sin embargo, no está del todo claro si y cómo obtiene acceso al
barvalor de retorno internobar:after. ¿Quizás podríamos (ab) usar lasuperpalabra clave?Reemplazo
El combinador before es equivalente a
prependmezclar con un método de anulación que llamasuperal final del método. Del mismo modo, el combinador posterior es equivalente aprependmezclar con un método de anulación que llamasuperal principio del método.También puede hacer cosas antes y después de llamar
super, puede llamarsupervarias veces, y recuperar y manipularsuperel valor de retorno, lo que lo haceprependmás poderoso que los combinadores de métodos.y
oldpalabra claveEsta idea agrega una nueva palabra clave similar a
super, que le permite llamar al método sobrescrito de la misma manera que lesuperpermite llamar al método reemplazado :El principal problema con esto es que es incompatible con versiones anteriores: si ha llamado al método
old, ¡ya no podrá llamarlo!Reemplazo
superen un método primordial en unprepended mixin es esencialmente el mismo queolden esta propuesta.redefpalabra claveSimilar a lo anterior, pero en lugar de agregar una nueva palabra clave para llamar al método sobrescrito y dejarlo
defsolo, agregamos una nueva palabra clave para redefinir los métodos. Esto es compatible con versiones anteriores, ya que la sintaxis actualmente es ilegal de todos modos:En lugar de agregar dos palabras clave nuevas, también podríamos redefinir el significado de
superinsideredef:Reemplazo
redefLa introducción de un método es equivalente a la anulación del método en unaprependcombinación.superen el método de anulación se comporta comosuperoolden esta propuesta.fuente
binda la mismaold_methodvariable?UnboundMethod#binddevolverá un nuevo, diferenteMethod, por lo tanto, no veo surgir ningún conflicto, independientemente de si lo llama dos veces seguidas o dos veces al mismo tiempo desde diferentes hilos.oldyredef? Mi 2.0.0 no los tiene. Ah, es difícil no perderse las otras ideas competidoras que no llegaron a Ruby fueron:Eche un vistazo a los métodos de alias, esto es como cambiar el nombre del método a un nuevo nombre.
Para obtener más información y un punto de partida, consulte este artículo sobre métodos de reemplazo (especialmente la primera parte). Los documentos de Ruby API también proporcionan (un ejemplo menos elaborado).
fuente
La clase que realizará la anulación debe volver a cargarse después de la clase que contiene el método original, por
requirelo tanto , en el archivo que realizará la anulación.fuente