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

时间:2023-01-19 19:19:41

I am still new to Ruby and basically just writing my first micro-program after finishing Cooper's book. I was pointed to the direction of avoiding monkey patching but the problem is I don't know what are the alternatives to achieve the same behavior. Basically, I want to add a new method that is accessible by every string object. The obvious monkey-patching way is to:

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

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

I recall there's a way using String.send. But I can't remember how it's done nor where I read it. Can anyone point out any alternatives that would still let me make that method available to the String class and child objects?

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

6 个解决方案

#1


15  

Any other way of doing this would just be a more awkward syntax for monkey patching. There are ways involving send and eval and all sorts of things, but why? Go ahead and do it the obvious way.

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

You want to be careful of monkey patching in big projects or when you have dependencies, because you can wind up with conflicts when several hands are all messing around in the same place. This doesn't mean look for an alternative syntax that accomplishes the same thing — it means be careful when you're making changes that could affect code that's not yours. This probably isn't a concern in your particular case. It's just something that might need to be addressed in larger projects.

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

One alternative in Ruby is that you can add methods to a single object.

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

#2


6  

If you want to add a new method that is accessible to every string object, then doing it the way you have it is how to get it done.

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

A good practice is to put your extensions to core objects in a separate file (like string_ex.rb) or a sub-directory(like extensions or core_ext). This way, it is obvious what has been extended, and it is easy for someone to see how they have been extended or changed.

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

Where monkey patching can go bad is when you change some existing behavior of a core object that causes some other code that expects the original functionality to misbehave.

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

#3


2  

The object class defines send, and all objects inherit this. You "send" an object the send method. The send method's parameters are the method-you-want-to-invoke's name as a symbol, followed by any arguments and an optional block. You can also use __send__.

对象类定义了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"]

Edit..

编辑..

You probably want to use the define_method method:

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

That adds instance methods. To add class methods:

这增加了实例方法。要添加类方法:

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

puts String.hello
# >> hello

You can't define methods that expect a block though.

但是,您无法定义期望块的方法。

It might be a good thing to have a read of this chapter from Why's Poignant Guide, you can skip down to "Dwemthy’s Array" to get to the meta-programming stuff.

从“为什么的尖锐指南”阅读本章可能是一件好事,你可以跳到“Dwemthy的数组”来获取元编程的东西。

#4


1  

Thanks guys.

多谢你们。

All of the suggested implementation work. More importantly, I learned to weigh in the case in hand and decide if re-opening core (or library) classes is a good idea or not.

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

FWIW, a friend pointed out the send implementation I was looking for. But now that I look at it, it's even closer to monkeypatching than all the other implementations :)

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

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

#5


0  

As an alternative to attaching functions to classes or objects, you can always go the functional route:

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

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

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

#6


0  

The "monkey patch" you describe could indeed be a problem if someone else wants to require your code (as a gem, for example). Who is to say they won't also want to add a String method that is called do_magic? One method will overwrite the other, and this can be challenging to debug. If there is any chance your code will be open source, then it is best to create your own class:

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

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

Now, if you need to do_magic you can

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

magic_str = MyString.new(str).do_magic

#1


15  

Any other way of doing this would just be a more awkward syntax for monkey patching. There are ways involving send and eval and all sorts of things, but why? Go ahead and do it the obvious way.

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

You want to be careful of monkey patching in big projects or when you have dependencies, because you can wind up with conflicts when several hands are all messing around in the same place. This doesn't mean look for an alternative syntax that accomplishes the same thing — it means be careful when you're making changes that could affect code that's not yours. This probably isn't a concern in your particular case. It's just something that might need to be addressed in larger projects.

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

One alternative in Ruby is that you can add methods to a single object.

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

#2


6  

If you want to add a new method that is accessible to every string object, then doing it the way you have it is how to get it done.

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

A good practice is to put your extensions to core objects in a separate file (like string_ex.rb) or a sub-directory(like extensions or core_ext). This way, it is obvious what has been extended, and it is easy for someone to see how they have been extended or changed.

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

Where monkey patching can go bad is when you change some existing behavior of a core object that causes some other code that expects the original functionality to misbehave.

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

#3


2  

The object class defines send, and all objects inherit this. You "send" an object the send method. The send method's parameters are the method-you-want-to-invoke's name as a symbol, followed by any arguments and an optional block. You can also use __send__.

对象类定义了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"]

Edit..

编辑..

You probably want to use the define_method method:

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

That adds instance methods. To add class methods:

这增加了实例方法。要添加类方法:

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

puts String.hello
# >> hello

You can't define methods that expect a block though.

但是,您无法定义期望块的方法。

It might be a good thing to have a read of this chapter from Why's Poignant Guide, you can skip down to "Dwemthy’s Array" to get to the meta-programming stuff.

从“为什么的尖锐指南”阅读本章可能是一件好事,你可以跳到“Dwemthy的数组”来获取元编程的东西。

#4


1  

Thanks guys.

多谢你们。

All of the suggested implementation work. More importantly, I learned to weigh in the case in hand and decide if re-opening core (or library) classes is a good idea or not.

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

FWIW, a friend pointed out the send implementation I was looking for. But now that I look at it, it's even closer to monkeypatching than all the other implementations :)

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

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

#5


0  

As an alternative to attaching functions to classes or objects, you can always go the functional route:

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

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

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

#6


0  

The "monkey patch" you describe could indeed be a problem if someone else wants to require your code (as a gem, for example). Who is to say they won't also want to add a String method that is called do_magic? One method will overwrite the other, and this can be challenging to debug. If there is any chance your code will be open source, then it is best to create your own class:

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

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

Now, if you need to do_magic you can

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

magic_str = MyString.new(str).do_magic