4

给出这里的简单示例:

class Base
  @tag = nil 

  def self.tag(v = nil) 
    return @tag unless v 
    @tag = v
  end 
end 

class A < Base 
  tag :A
end

class B < Base
  tag :B
end 

class C < Base; end

puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"

按预期工作

A: A
B: B 
A: A
C: 

我想创建一个模块,base 将扩展以提供相同的功能,但具有类指定的所有标记信息。例如。

module Tester 
  def add_ident(v); ....; end
end

class Base 
  extend Tester 

  add_ident :tag
end 

我发现我可以通过直接评估来做到这一点,所以:

def add_ident(v)
  v = v.to_s 
  eval "def self.#{v}(t = nil); return @#{v} unless t; @#{v} = t; end"
end

但我真的不喜欢在任何语言中使用 eval 字符串。

有没有一种方法可以在不使用 eval 的情况下获得此功能?我已经完成了我能想到的 define_method 和 instance_variable_get/set 的所有组合,但我无法让它工作。

没有 Rails 的 Ruby 1.9。

4

4 回答 4

3

您想在要扩展的类的单例类上定义一个动态方法。一个类的单例类可以用这样的表达式来访问class << self; self end:要打开一个类的类的范围,您可以使用class_eval. 把所有这些放在一起,你可以写:

module Identification

  def add_identifier(identifier)
    (class << self; self end).class_eval do
      define_method(identifier) do |*args|
        value = args.first
        if value
          instance_variable_set("@#{identifier}", value)
        else
          instance_variable_get("@#{identifier}")
        end
      end
    end
  end

end

class A
  extend Identification

  add_identifier :tag
end

如果您使用的是最新版本的 Ruby,这种方法可以替换为Module#define_singleton_method

module Identification

  def add_identifier(identifier)
    define_singleton_method(identifier) do |value = nil|
      if value
        instance_variable_set("@#{identifier}", value)
      else
        instance_variable_get("@#{identifier}")
      end
    end
  end

end

我不相信您想使用self.class.send(:define_method),如此处的另一个答案所示;这具有将动态方法添加到 的所有子类的意外副作用 self.classA在我的示例中是Class.

于 2012-10-09T03:08:25.023 回答
1

我最喜欢的 Ruby 书籍Metaprogramming Ruby用以下方式解决了这些问题:

module AddIdent 
  def self.included(base)
    base.extend ClassMethods    # hook method
  end

  module ClassMethods
    def add_ident(tag)
      define_method "#{tag}=" do |value=nil|
        instance_variable_set("@#{tag}", value)
      end

      define_method tag do 
        instance_variable_get "@#{tag}"
      end 
    end
  end
end 

# And use it like this
class Base
  include AddIdent

  add_ident :tag
end
于 2012-10-09T03:00:20.003 回答
1
module Tester
  def add_ident(var)
    self.class.send(:define_method, var) do |val=nil|
        return instance_variable_get("@#{var}") unless val
        instance_variable_set "@#{var}", val
      end
    end
end
于 2012-10-09T02:41:22.667 回答
0

呸,不总是这样,一旦你感到沮丧到发帖,你就会找到答案:)

诀窍似乎在于 (class << self; self; end) 在不破坏本地范围的情况下为您提供类实例。参考:如何使用define_method创建类方法?

def add_ident(v) 
  var_name = ('@' + v.to_s).to_sym 
  (class << self; self; end).send(:define_method, v) do |t = nil|
    return instance_variable_get(var_name) unless t
    instance_variable_set(var_name, t)
  end 
end 

如果他们出现,我会接受更好的答案。

于 2012-10-09T02:38:52.543 回答