Ruby中的method_missing和define_method

时间:2014-08-29 19:17:31

标签: ruby

有以下代码:

class MyOpenStruct
    def initialize(initial_values = {})
    @values = initial_values
    end

    def _singleton_class
        class << self 
            self
        end 
    end

    def method_missing(name, *args, &block) 
        if name[-1] == "="
            base_name = name[0..-2].intern 
            puts "add_method_to_set"
            self.class.add_method_to_set(base_name)
      @values[base_name] = args[0]
     else
            puts "add_method_to_get"
            self.class.add_method_to_get(base_name)         
        @values[name]
        end 
    end

    def self.add_method_to_get(name)
        define_method(name) do |value|
            @values[name]
        end
    end

    def self.add_method_to_set(name)
        define_method(name) do |value|
            @values[name] = value
        end
    end
end

obj1 = MyOpenStruct.new(name: "Dave") 
obj1.address = "1"

obj2 = MyOpenStruct.new(name: "Dave") 
obj2.address = "2"

我想做以下事情:当我执行某个方法(obj1.address)并且它丢失时我想将此方法添加到MyOpenStruct类中。但是当我执行我的代码时,我会“错过”两次而不是一次。为什么?我不明白。请向我解释一下。谢谢。

2 个答案:

答案 0 :(得分:0)

@koffeinfrei发现你的代码存在一个问题,但我发现了其他一些问题。下面我有我认为是更正版本。我还提出了一种构造代码的替代方法。我的主要建议是提取实例方法的动态创建,因为它非常通用。您甚至可以将其放在一个模块中,其中包含您可以根据需要包含的其他方法。

您的维修代码

class MyOpenStruct
  def initialize(initial_values = {})
  @values = initial_values
  end

  def method_missing(name, *args, &block) 
  puts "in mm, name = #{name}"
    if name[-1] == "="
      base_name = name[/\w+/]
      puts "add_method_to_set: '#{name}'"
      self.class.add_method_to_set(base_name)
      @values[base_name.to_sym] = args[0]
    else
      puts "add_method_to_get: '#{name}'"
      self.class.add_method_to_get(name)         
      @values[name.to_sym]
    end 
  end

  def self.add_method_to_get(name)
    define_method(name.to_sym) do
      @values[name.to_sym]
    end
  end

  def self.add_method_to_set(name)
    define_method((name+'=').to_sym) do |value|
      @values[name.to_sym] = value
    end
  end
end

替代建筑

def create_instance_eval(klass, method, &block)
  klass.class_eval { define_method(method, &block) }
end

class MyOpenStruct
  def initialize(initial_values = {})
    @values = initial_values
  end

  def method_missing(name, *args, &block) 
    if name[-1] == "="
      base_name = name[/\w+/]
      method_name = (base_name+'=').to_sym
      puts "create method '#{method_name}'"
      method = create_instance_eval(self.class, method_name) do |value|
          @values[base_name.to_sym] = value
        end
      send(method, args[0])
    else
      method_name = name.to_sym
      puts "create method '#{method_name}'"
      method = create_instance_eval(self.class, method_name) do
        @values[method_name]
      end
      send(method)
    end 
  end
end

示例

MyOpenStruct.instance_methods(false)
  #=> [:method_missing]

obj1 = MyOpenStruct.new(name: "Dave") 
  #=> #<MyOpenStruct:0x00000102805b58 @values={:name=>"Dave"}>
obj1.address = "1"
  # create method 'address='
  #=> "1"

MyOpenStruct.instance_methods(false)
  #=> [:method_missing, :address=]
obj2 = MyOpenStruct.new(name: "Mitzy") 
  #=> #<MyOpenStruct:0x00000101848878 @values={:name=>"Mitzy"}>
obj2.address = 2
  #=> 2

obj2.address
  # create method 'address'
  # => 2

MyOpenStruct.instance_methods(false)
  $#=> [:method_missing, :address=, :address]

obj1.instance_variable_get(:@values)
  #=> {:name=>"Dave", :address=>"1"}
obj2.instance_variable_get(:@values)
  #=> {:name=>"Mitzy", :address=>2}

答案 1 :(得分:0)

setter方法的方法名称需要包含尾随 = ,因此您需要使用name而不是base_name来定义方法。

self.class.add_method_to_set(name)