猴子修补核心类的替代方案

时间:2009-03-29 21:45:15

标签: ruby oop monkeypatching

我还是Ruby的新手,基本上只是在完成Cooper的书之后编写我的第一个微程序。我被指向避免猴子修补的方向,但问题是我不知道有什么替代方法可以实现相同的行为。 基本上,我想添加一个每个字符串对象都可以访问的新方法。明显的猴子修补方式是:

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

我记得有一种使用String.send的方法。但我不记得它是如何完成的,也不记得我在哪里阅读它。 任何人都可以指出任何仍然允许我将该方法用于String类和子对象的替代方法吗?

6 个答案:

答案 0 :(得分:15)

执行此操作的任何其他方式只是猴子修补的更尴尬的语法。有一些方法涉及sendeval以及各种各样的事情,但为什么?来吧,以明显的方式做到这一点。

你想要在大型项目中或者当你有依赖关系时要小心猴子补丁,因为当几只手都在同一个地方搞乱时,你可以结束冲突。这并不意味着寻找可以完成同样事情的替代语法 - 这意味着当您进行可能影响不属于您的代码的更改时要小心。在您的特定情况下,这可能不是一个问题。这只是需要在大型项目中解决的问题。

Ruby中的一个替代方案是您可以将方法添加到单个对象。

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

答案 1 :(得分:6)

如果你想添加一个每个字符串对象都可以访问的新方法,那么按照它的方式进行操作就是如何完成它。

一个好的做法是将核心对象的扩展放在单独的文件(如string_ex.rb)或子目录(如extensionscore_ext)中。这样,很明显扩展了什么,并且很容易让人看到它们是如何被扩展或改变的。

猴子修补可能会变坏的地方是当您更改核心对象的某些现有行为时,会导致某些其他代码期望原始功能行为不当。

答案 2 :(得分:2)

object类定义send,所有对象都继承此项。您使用send方法“发送”对象。 send方法的参数是您想要调用的方法的名称作为符号,后跟任何参数和可选块。您也可以使用__send__

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

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

修改..

您可能想要使用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

添加实例方法。要添加类方法:

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

puts String.hello
# >> hello

您无法定义期望阻止的方法。

阅读this chapter from Why's Poignant Guide可能是一件好事,你可以跳到“Dwemthy的数组”来获取元编程的东西。

答案 3 :(得分:1)

谢谢你们。

所有建议的实施工作。更重要的是,我学会了掌握案例,并决定是否重新开放核心(或图书馆)课程是个好主意。

FWIW,一位朋友指出我正在寻找的send实施。但是现在我看一下它,它比monkeypatching更接近于所有其他实现:)

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

答案 4 :(得分:0)

作为将函数附加到类或对象的替代方法,您始终可以使用功能路径:

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

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

答案 5 :(得分:0)

如果其他人想要您的代码(例如,作为宝石),您描述的“猴子补丁”确实可能是一个问题。谁会说他们也不想添加名为do_magic的String方法?一种方法会覆盖另一种方法,这对调试来说可能很有挑战性。如果您的代码有可能是开源的,那么最好创建自己的类:

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

现在,如果你需要do_magic,你可以

magic_str = MyString.new(str).do_magic