有没有办法重新定义块中的方法?

时间:2019-04-30 20:29:19

标签: ruby

假设我想puts用大写字母{em> 编写大量字符串,但是为了简洁起见,我想每次写puts(string)而不是puts(string.upcase)

我有办法像这样重新定义puts 仅在一个区块内吗?

# This will puts as normal
puts "abc"
# => abc

# This will puts an upcased string, but it's too verbose
puts "abc".upcase
# => ABC

# I want to do something like this, which will override the puts method for code run within the block
def always_upcase_strings(&block)
  def puts(string)
    super(string.upcase)
  end
end

always_upcase_strings do 
  puts "abc"
  puts "def"
  puts "ghi"
end
puts "xyz"
# => ABC
# => DEF
# => GHI
# => xyz

我上面的示例得到了简化-我不是用puts而是我自己编写的方法。

2 个答案:

答案 0 :(得分:3)

second thoughts上,这是一个更好的答案:

@CsvRecord(separator = ",",skipFirstLine = true)

public class Sample
{

 //some fields

}

creates临时类(匿名,因为它不需要名称),具有所需的方法覆盖。

然后在该类实例的上下文中调用产生的块

与我的“重新定义-未定义-重新定义方法”解决方案相比,该方法不但令人困惑,而且还具有线程安全的优点。因此,例如,在调用方法的同时运行并行测试,您将不会得到怪异的行为。

...但是我仍然坚持最初的说法,即在一个块内重新定义方法是非常令人惊讶的行为。同事可能不喜欢您选择那种设计模式(除非以有限的方式进行,并且有充分的理由!)

答案 1 :(得分:1)

这有点疯狂;我不建议在生产环境中运行类似的东西...但是我很好奇,并找到了一种使用ruby的元编程来做到这一点的方法:

def always_upcase_strings(&block)
  original_puts = method(:puts)
  define_method(:puts) do |string|
    original_puts.call(string.upcase)
  end
  yield
  undef :puts
  define_method(:puts, original_puts.unbind)
end

always_upcase_strings do
  puts "abc" #=> "ABC"
  puts "def" #=> "DEF"
  puts "ghi" #=> "GHI"
end

puts "xyz"   #=> "xyz"

这是怎么回事?

  • 首先,我抓住puts Method object,并将其存储在变量中。
  • 然后,使用所需的修改将puts重新定义为invoke it。 (我也可以使用super(string.upcase)来做到这一点,但是我认为上面的内容更好地说明了发生了什么。)
  • 然后yield到一个块,以便您可以调用该方法的新版本。
  • 接下来,undef方法(!!!)完全摆脱了覆盖。 (但是不幸的是,这也删除了原始方法。所以...)
  • 最后,re-define the puts method by re-binding the orignal回到self

这是绝对的黑魔法。使用后果自负。


相反,我的“现实世界”建议是:

def puts_upcase(str)
  puts str.upcase
end

puts_upcase "abc" #=> "ABC"
puts "xyz"        #=> "xyz"

或者在您的情况下,如果您实际上想调整类方法的行为,也许Module#prepend是工作的更好工具。

简单的解决方案通常是更好的解决方案。