0

我有很多这样的方法:

def create_machine(name, os_type_id, settings_file='', groups=[], flags={})
  soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
  args = method(__method__).parameters.map { |arg| arg[1] }
  soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
  VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(soap_message))
end

def register_machine(machine)
  soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
  args = method(__method__).parameters.map { |arg| arg[1] }
  soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
  VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(soap_message))
end

它们具有相同的实现,但不同数量的不同参数。在数十个类中的每一个中都会有数十个这样的方法。所以我想我会使用一些元编程来最小化代码重复。我试图通过这样做,define_method并希望以这样的方式结束:

vb_method :create_machine, :args => [:name, :os_type_id], :optional_args => [:settings_file, :groups, :flags]

但是我找不到将任意数量的命名(非 splat)参数传递给define_method的方法(我认为 splat 参数会使记录方法变得难以甚至不可能,也会使生成的 API 不方便)。

处理这个问题的最佳方法是什么(使用 Ruby 2.0)?

UPD 另一种方法是定义一个方法 vb_method:

def vb_method(*vb_meths)
  vb_meths.each do |meth|
    define_method(meth) do |message={}|
      soap_method = "#{self.class.name.split('::').last.to_underscore}_#{meth}".to_sym
      VirtualBoxAPI.send_request(@cl.conn, soap_method, @this.merge(message))
    end
  end
end

然后班级会有这样的电话:

vb_method :create_machine, :register_machine

但在这种情况下,我将需要始终以 hash 作为参数调用方法:

machine = vb.create_machine(name: 'my_vm', os_type_id: 'Windows95')

这正是我要避免的,因为我认为在这种情况下,生成的 API 无法记录并且使用起来不方便。

4

1 回答 1

3

停止尝试避免选项哈希。这就是做事的“Ruby 方式”。它们并非不可能记录,并且几个主流 Ruby 库以这种方式使用它们(首先想到的是 ActiveRecord 和 Mysql2)。

请注意,您可以为选项哈希提供默认参数,该参数用作文档并允许您减少代码重复。

另外,考虑一下如果您可以(以某种方式)将任意数量的命名参数传递给define_method. 用户如何记住哪些参数是哪些?他们需要记住以这种方式定义的所有不同方法的所有不同位置参数的顺序和含义。当您有许多具有不同含义的参数的类似方法时,很难保持一切正常。关键字参数(本质上是 Ruby 的选项哈希)是专门为避免这种情况而创建的。

如果您担心错误检查,请定义一个辅助方法来检查选项哈希是否存在丢失/无法识别的键并引发信息异常:

def validate_options(known, opts)
  opts.each_key { |opt| raise "Unknown option: #{opt}" unless known.include?(opt) }
  known.each { |opt, required| raise "Missing required option: #{opt}" if required and not opts.include?(opt) }
end
于 2013-09-29T16:16:23.330 回答