从Ruby核心类中调用自定义方法

时间:2017-12-17 15:22:44

标签: ruby oop

TL; DR;
如何在Ruby核心类中调用一个方法(在不知名的地方编写)?

我正在编写管理文本文件的脚本。这是我的代码:

File.open("file.txt", "w").each do |f|
  f.puts "Text to be inserted"
  f.puts text_generated
  f.puts "Some other text" if some_condition?
  f.puts ""
end

我想通过引入一个方法来清理代码:

File.open("file.txt", "w").each do |f|
  f.puts_content(text_generated, some_condition?
  # -> Generates an error: private method called for <File> (NoMethodError)
end

def puts_content(text, cond)
  puts "Text to be inserted"
  puts text
  puts "Some other text" if cond
  puts ""
end

但事实上,由于私有方法访问,此方法无法在File类中调用。

任何人都可以解释这个错误,以及我该怎么做?

我的解决方法是在继承自MyFile的自定义File类中编写这些方法:

MyFile.open("file.txt", "w").each do |f|
  f.puts_content  # Seems to work
end

class MyFile < File
  def puts_content(cond)
    puts "Text to be inserted"
    puts text_generated_elsewhere
    puts "Some other text" if cond
    puts ""
  end
end

我可以将这些内容直接放在File中,但在触及语言核心库时我会胆怯。

我想知道这是否是一个很好的方法。

可以从其他核心模块/类调用Ruby核心方法。这是否意味着所有核心模块/类包含或需要彼此?它是如何在引擎盖下工作的?

2 个答案:

答案 0 :(得分:1)

在顶层定义方法时,会在Object上添加一个实例方法,因此可供后代类(大多数其他核心类)访问

def foo
  1
end
method(:foo)
# => #<Method: Object#foo>

然而,此方法的访问级别在IRB / pry中似乎与运行脚本时不同。

在IRB:

puts [].foo
# => 1

在剧本中:

puts [].foo
# => NoMethodError (private method called...)

当然,您始终只需使用send调用私有方法:

[].send(:foo)
# or, in your case, f.send(:puts_content, text_generated, some_condition?)

此外,在任何情况下都不会覆盖后代类上的方法(如果已经定义了它):

def length
  1
end
puts [].length
# => 0

你的第二种方法(直接修补核心类)将起作用,如果已经在文件中定义了puts_content(它不是),它将覆盖puts_content。但是,如果您想避免修补核心类,我建议采用两种方法:

  1. 使用静态(类)方法并将文件对象作为参数传递

    class FileUtils
      def self.puts_content(file, text, cond)
        file.puts "Text to be inserted"
        file.puts text
        file.puts "Some other text" if cond
        file.puts ""
      end
    end
    File.open("file.txt", "w").each do |f|
      FileUtils.puts_content(f, text_generated, some_condition?)
    end
    
  2. 使用细化:

    module FileUtils
      refine File do
        def puts_content(text, cond)
          puts "Text to be inserted"
          puts text
          puts "Some other text" if cond
          puts ""
        end
      end
    end
    
    # elsewhere ...
    using FileUtils
    
    File.open("file.txt", "w").each do |f|
      f.puts_content(f, text_generated, some_condition?)
    end
    

    您可以阅读有关优化here的内容,但实质上,它们只是修补某个文件或类中的核心类的方法。这为您提供了良好,简洁的猴子修补语法的好处,同时降低了改变其他地方定义的行为的风险。

答案 1 :(得分:0)

关于你的第一个问题。您收到错误是因为该方法未在File类中定义。所以你无法像f.puts_content那样调用它。

您可以定义一个接收File作为参数puts_content(file, ...)的方法。

关于问题的第二部分,我这是一个很好的解决方案(思考面向对象)。