1

我有几个类,每个类都定义了各种统计数据。

class MonthlyStat
  attr_accessor :cost, :size_in_meters
end

class DailyStat
  attr_accessor :cost, :weight
end

我想为这些对象的集合创建一个装饰器/演示者,这让我可以轻松访问有关每个集合的聚合信息,例如:

class YearDecorator
  attr_accessor :objs
  def self.[]= *objs
    new objs
  end
  def initialize objs
    self.objs = objs
    define_helpers
  end

  def define_helpers
    if o=objs.first # assume all objects are the same
      o.instance_methods.each do |method_name|
        # sums :cost, :size_in_meters, :weight etc
        define_method "yearly_#{method_name}_sum" do
          objs.inject(0){|o,sum| sum += o.send(method_name)}
        end
      end
    end
  end
end

YearDecorator[mstat1, mstat2].yearly_cost_sum

不幸的是,实例方法中没有定义方法。

将其替换为:

class << self
  define_method "yearly_#{method_name}_sum" do
    objs.inject(0){|o,sum| sum += o.send(method_name)}
  end
end

...也失败了,因为实例中定义的变量 method_name 和 objs 不再可用。在红宝石中是否有一个惯用的方法来完成这个?

4

2 回答 2

1

(编辑:我明白你现在想要做什么。)

好吧,我尝试了您可能做过的相同方法,但最终不得不使用eval

class Foo
  METHOD_NAMES = [:foo]

  def def_foo
    METHOD_NAMES.each { |method_name|
      eval <<-EOF
        def self.#{method_name}
          \"#{method_name}\".capitalize
        end
      EOF
    }
  end
end

foo=Foo.new

foo.def_foo
p foo.foo # => "Foo"

f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."

我自己承认这不是最优雅的解决方案(甚至可能不是最惯用的),但我过去遇到过类似的情况,其中最直接的方法是eval.

于 2013-08-29T01:26:32.353 回答
0

我很好奇你要买什么o.instance_methods。这是一个类级别的方法,通常不适用于对象实例,据我所知,这就是您在此处处理的内容。

无论如何,您可能正在寻找method_missing,它将在您第一次调用它时动态定义方法,并让您发送:define_method到对象的类。您无需在每次实例化新对象时重新定义相同的实例方法,因此method_missing仅当尚未定义调用的方法时才允许您在运行时更改类。

由于您期望其他类中的方法名称被某种模式包围(即,yearly_base_sum将对应于一个base方法),因此我建议编写一个方法,如果找到匹配模式则返回匹配模式。NoMethodError注意:这不会涉及在另一个类上制作方法列表 -当您的一个对象不知道如何响应您发送的消息时,您仍然应该依赖内置方法。这使您的 API 更加灵活,并且在您的统计信息类也可能在运行时修改的情况下很有用。

def method_missing(name, *args, &block)
  method_name = matching_method_name(name)
  if method_name
    self.class.send :define_method, name do |*args|
      objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
    end
    send name, *args, &block
  else
    super(name, *args, &block)
  end
end

def matching_method_name(name)
  # ... this part's up to you
end
于 2013-08-28T22:48:34.043 回答