2009-03-29 7 views
9

Todavía soy nuevo en Ruby y, básicamente, estoy escribiendo mi primer microprograma después de terminar el libro de Cooper. Me señalaron la dirección para evitar parches de mono, pero el problema es que no sé cuáles son las alternativas para lograr el mismo comportamiento. Básicamente, quiero agregar un nuevo método al que pueda acceder cada objeto de cadena. La forma obvia de parche de monos es:Alternativas a las clases básicas de reparación de monos

class String 
    def do_magic 
    ...magic... 
    end 
end 

Recuerdo que hay una manera de usar String.send. Pero no recuerdo cómo se hizo ni dónde lo leí. ¿Alguien puede señalar alguna alternativa que aún me permita poner ese método a disposición de la clase String y los objetos secundarios?

Respuesta

15

Cualquier otra forma de hacer esto sería una sintaxis más incómoda para el parche de mono. Hay formas que involucran send y eval y todo tipo de cosas, pero ¿por qué? Adelante y hazlo de la manera obvia.

Desea tener cuidado con los parches de monos en proyectos grandes o cuando tiene dependencias, porque puede terminar con conflictos cuando varias manos están jugando en el mismo lugar. Esto no significa buscar una sintaxis alternativa que logre lo mismo; significa tener cuidado cuando se realizan cambios que podrían afectar el código que no es el suyo. Esto probablemente no es una preocupación en su caso particular. Es solo algo que podría necesitar ser abordado en proyectos más grandes.

Una alternativa en Ruby es que puede agregar métodos a un solo objeto.

a = "Hello" 
b = "Goodbye" 
class <<a 
    def to_slang 
    "yo" 
    end 
end 
a.to_slang # => "yo" 
b.to_slang # NoMethodError: undefined method `to_slang' for "Goodbye":String 
+0

puedo ver cómo es realmente depende de la situación y la naturaleza del proyecto en sí. Y creo que esto también se aplicaría a todos los pares do/do not. Considerando mi caso particular, sí, tiene sentido que no duela. Después de todo, estoy agregando un nuevo método. ¡Gracias por la aclaración! – dmondark

+0

El parche de mono es una pendiente resbaladiza, en el mejor de los casos. Puede parecer elegante para equipos pequeños en proyectos de código cerrado, pero nunca se sabe cuándo su código fuente puede ser empaquetado como una gema o copiado por alguien más adelante, y si quieren que su Cadena haga do_magic de una manera ligeramente diferente, luego, un método parche mono sobrescribirá al otro y no se lanzará ninguna advertencia. Es una receta para errores difíciles de encontrar. Es especialmente problemático si está sobrescribiendo los métodos de las clases base incluidas con Ruby. – emery

2

La clase define objectsend, y todos los objetos heredan esto. Usted "envía" un objeto al método send. Los parámetros del método send son el nombre del método que desea invocar como un símbolo, seguido de cualquier argumento y un bloque opcional. También puede usar __send__.

>> "heya".send :reverse 
=> "ayeh" 

>> space = %w(moon star sun galaxy) 
>> space.send(:collect) { |el| el.send :upcase! } 
=> ["MOON", "STAR", "SUN", "GALAXY"] 

Editar ..

Es posible que desee utilizar el método define_method:

String.class_eval { 
    define_method :hello do |name| 
    self.gsub(/(\w+)/,'hello') + " #{name}" 
    end 
} 

puts "Once upon a time".hello("Dylan") 
# >> hello hello hello hello Dylan 

que añade los métodos de instancia. Para agregar métodos de clase:

eigenclass = class << String; self; end 
eigenclass.class_eval { 
    define_method :hello do 
    "hello" 
    end 
} 

puts String.hello 
# >> hello 

Sin embargo, no puede definir los métodos que esperan un bloque.

Puede ser bueno tener una lectura de this chapter from Why's Poignant Guide, puede saltar a "Dwemthy's Array" para llegar a la meta-programación.

+0

Estaba tratando de evitar temporalmente entrar en la meta-programación por el momento. Pero supongo que es inevitable si quiero superar las cosas hola-mundo-ish. Mi idea sobre el uso de un evals es básicamente el tiempo de ejecución-monkeypatching. Pero ahora veo que todo lo demás es solo eso. – dmondark

6

Si desea agregar un nuevo método que sea accesible para cada objeto de cadena, entonces hacerlo de la manera en que lo tiene es cómo hacerlo.

Una buena práctica es colocar sus extensiones en los objetos principales en un archivo separado (como string_ex.rb) o en un subdirectorio (como extensions o). De esta manera, es obvio lo que se ha extendido, y es fácil para alguien ver cómo se han ampliado o modificado.

Donde el parche de mono puede salir mal es cuando cambia algún comportamiento existente de un objeto central que causa algún otro código que espera que la funcionalidad original se comporte mal.

1

Gracias chicos.

Todo el trabajo de implementación sugerido. Más importante aún, aprendí a sopesar el caso en cuestión y decidir si la reapertura de las clases principales (o de biblioteca) es una buena idea o no.

FWIW, un amigo señaló la implementación send que estaba buscando. Pero ahora que lo miro, es incluso más cerca de monkeypatching que todas las otras implementaciones :)

module M 
    def do_magic 
    .... 
    end 
end 
String.send(:include, M) 
0

Como alternativa a la fijación de las funciones a clases u objetos, siempre se puede ir a la ruta funcional:

class StringMagic 
    def self.do(string) 
    ... 
    end 
end 

StringMagic.do("I'm a String.") # => "I'm a MAGIC String!" 
0

El "parche de mono" que describes podría ser un problema si alguien más desea solicitar tu código (como una gema, por ejemplo). ¿Quién puede decir que ellos tampoco querrán agregar un método String que se llame do_magic? Un método sobrescribirá al otro, y esto puede ser difícil de depurar. Si hay alguna posibilidad de que su código será de código abierto, entonces lo mejor es crear su propia clase:

class MyString < String 
    def initialize(str) 
    @str = str 
    end 
    def do_magic 
    ...magic done on @str 
    @str 
    end 
end 

Ahora, si necesita do_magic puedo

magic_str = MyString.new(str).do_magic 
Cuestiones relacionadas