ruby Monkey修补核心类的替代方法

jdgnovmf  于 2022-12-18  发布在  Ruby
关注(0)|答案(7)|浏览(111)

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

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

我记得有一种方法可以使用String.send,但是我不记得它是怎么做的,也不记得我是在哪里读到它的。谁能指出任何替代方法,让我仍然可以使用String类和子对象的方法?

50pmv0ei

50pmv0ei1#

任何其他的方法都只是猴子修补的语法上的一个更笨拙的方法。有一些方法涉及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
5rgfhyps

5rgfhyps2#

如果你想添加一个每个字符串对象都可以访问的新方法,那么按照你已有的方法去做就是如何完成它。
一个好的做法是把你的核心对象扩展放在一个单独的文件中(比如string_ex.rb)或者一个子目录中(比如extensions或者core_ext),这样的话,很明显什么被扩展了,并且人们很容易看到它们是如何被扩展或者改变的。
猴子修补可能出现问题的地方是,当您更改核心对象的某些现有行为时,会导致其他一些预期原始功能会出现问题的代码出现问题。

rjee0c15

rjee0c153#

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的数组”来了解元编程的内容。

r1wp621o

r1wp621o4#

谢谢你们。
更重要的是,我学会了权衡手头的情况,并决定重新打开核心(或库)类是否是一个好主意。
FWIW,一个朋友指出了我一直在寻找的send实现。但是现在我看了一下,它比所有其他实现更接近monkeypatching:)

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

nbysray55#

作为将函数附加到类或对象的替代方法,您总是可以走函数路线:

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

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

u1ehiz5o6#

如果其他人想使用你的代码(比如gem),你所描述的“猴子补丁”确实会是个问题。谁能说他们不会也想添加一个名为do_magic的String方法呢?一个方法会覆盖另一个方法,这可能会很难调试。如果你的代码有可能是开源的,那么最好创建你自己的类:

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

现在,如果你想变魔术你可以

magic_str = MyString.new(str).do_magic
92vpleto

92vpleto7#

您可以尝试refinements

module StringPatch
  refine String do
    def downcase
      self.reverse # for demonstration purposes
    end
  end
end

使用它:

require_relative 'string_patch'

module Foo
  using StringPatch

  def self.bar
    'BAR'.downcase
  end
end

p Foo.bar # => RAB
p 'BAR'.downcase # => bar

在这里,我们将monkeypatch严格地命名为module Foo作用域,正常的功能在模块之外恢复,这也适用于类,我们也可以将作用域定义为文件:

require_relative 'string_patch'

p 'BAR'.downcase # => bar

using StringPatch

module Foo
  def self.bar
    'BAR'.downcase
  end
end

p Foo.bar # => RAB
p 'BAR'.downcase # => RAB


如果benefits aren't clear,命名空间monkeypatch提供了在特定模块、类或文件中使用流畅的monkeypatch语法的能力,而在使用其他monkeypatch String的库(如activesupport)时没有冲突的风险。
有关详细信息,请参见the docs

相关问题