【问题标题】:Creating class methods from a module从模块创建类方法
【发布时间】:2012-09-29 07:29:26
【问题描述】:

这里举个简单的例子:

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。

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    您想在要扩展的类的单例类上定义一个动态方法。类的单例类可以使用如下表达式访问:class &lt;&lt; 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.class 的所有子类的意外副作用,在我的示例中AClass

    【讨论】:

      【解决方案2】:
      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
      

      【讨论】:

      • 我想它定义了Module的方法。
      【解决方案3】:

      我最喜欢的红宝石书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
      

      【讨论】:

        【解决方案4】:

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

        诀窍似乎在于 (class How do I use define_method to create class methods?

        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 
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-14
          • 2021-12-05
          • 1970-01-01
          • 2015-07-11
          • 2014-12-25
          相关资源
          最近更新 更多