2012-04-26 9 views
25

me he dado cuenta de que hay dos formas comunes de mono parchear una clase en rubí:enfoque recomendado para parchear una clase de mono en rubí

Definir los nuevos miembros de la clase de este modo:

class Array 
    def new_method 
    #do stuff 
    end 
end 

y llamando class_eval en la clase de objeto:

Array.class_eval do 
    def new_method 
     #do stuff 
    end 
end 

me pregunto si hay alguna diferencia entre los dos y si hay ventajas de utilizar un enfoque sobre el otro?

+0

Posible duplicado de [parches mono vs clase \ _eval?] (Http://stackoverflow.com/questions/9399358/monkey-patching-vs-class-eval) – akostadinov

Respuesta

54

Honestamente, solía usar la primera forma (reapertura de la clase), ya que se siente más natural, pero su pregunta me obligó a investigar sobre el tema y aquí está el resultado.

El problema con la reapertura de la clase es que definirá silenciosamente una nueva clase si la original, que tenía la intención de reabrir, por alguna razón no estaba definida en este momento. El resultado podría ser diferente:

  1. Si no se sobreescribe cualquier método, pero solo agregue las nuevas y se define la aplicación original (por ejemplo, archivo, donde la clase se define inicialmente es cargado) luego todo irá estar bien

  2. Si redefine algunos métodos y el original se carga más tarde, sus métodos se redefinirán con sus versiones originales.

  3. El caso más interesante es cuando se usa el estándar autoloading o algún mecanismo de recarga elegante (como el utilizado en Rails) para cargar/recargar clases. Algunas de estas soluciones se basan en const_missing que se invoca cuando hace referencia a una constante no definida. En ese caso, el mecanismo de carga automática intenta encontrar una definición de clase indefinida y cargarla. Pero si está definiendo clases por su cuenta (aunque tenía la intención de reabrir el ya definido), ya no 'desaparecerá' y el original nunca se cargará del todo ya que el mecanismo de carga automática no se activará.

Por otro lado, si se utiliza class_eval Se le notificará al instante si la clase no se define por el momento. Además, como hace referencia a la clase cuando llama al método class_eval, cualquier mecanismo de carga automática tendrá la posibilidad de localizar la definición de la clase y cargarla.

Teniendo esto en mente class_eval parece ser un mejor enfoque. Sin embargo, me gustaría escuchar alguna otra opinión.

+0

buena investigación :) –

+0

Google es una herramienta bastante poderosa después de todo =) –

6

Ámbito

Una gran diferencia que, creo, KL-7 no señaló es el ámbito en el que se interpretará su nuevo código:

Si está (re) apertura de una Para manipularla, el nuevo código que agregue se interpretará en el ámbito de la clase (original).
Si está utilizando Module#class_eval para manipular una clase, el nuevo código que agregue se interpretará en el ámbito de su llamada a #class_eval y NO tendrá conocimiento del alcance de clase. Si no se sabe, este comportamiento puede ser contra-intuitivo y conducir a errores difíciles de depurar.

CONSTANT = 'surrounding scope' 

# original class definition (uses class scope) 
class C 
    CONSTANT = 'class scope' 

    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope" 


# monkey-patching with #class_eval: uses surrounding scope! 
C.class_eval do 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "surrounding scope" 


# monkey-patching by re-opening the class: uses scope of class C 
class C 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope" 
Cuestiones relacionadas