Ruby 动态创建方法和变量

Ruby dynamically create methods and variables

如何用更明智的方法替换 add_entry 方法?

class MyStorageClass

    def add_entry key, value
        eval "(@#{key} ||= []) << value; def #{key}; @#{key}; end"
    end

end

然后我可以按如下方式检索值:

def get_entry key
    begin
        self.send key.to_sym
    rescue NoMethodError
        nil
    end
end

我不确定你所谓的“更明智”是什么,但这是没有 evals 开头的模板:

def add_entry key, value
  # define instance variable unless it is already defined
  instance_variable_set :"@#{key}", [] \
    unless instance_variable_defined? :"@#{key}"
  # add value to the array
  instance_variable_set :"@#{key}", instance_variable_get(:"@#{key}") + value
  # define getter
  self.class.send :define_method key { instance_variable_get :"@#{key}" } \
    unless self.class.instance_methods.include?(key)
end

getter 的定义可能更具可读性:

  self.class.send :attr_reader, key \
    unless self.class.instance_methods.include?(key)

这可以使用 instance_variable_set and attr_accessor 来实现:

class MyStorageClass
  def add_entry(key, value)
    if respond_to?(key)
      key << value
    else
      instance_variable_set("@#{key}", [value])
      self.class.send(:attr_accessor, key)
    end
  end
end

但是 正如其他人所建议的,更简洁的方法是简单地使用 Hash 而不是为每个变量定义一个新的实例方法。

而不是每个键一个实例变量,这需要一些不必要的笨重代码,为什么不像下面那样只有一个哈希。另外,define_methoddefine_singleton_method 可以成为你的朋友,避免坏坏 eval

class MyStorageClass
  def initialize
    @data = {}
  end

  def add_entry(key, value)
    (@data[key] ||= []) << value
    define_singleton_method(key){ @data[key] }
  end

  def get_entry(key)
    @data.key?(key) or raise NoMethodError
    @data[key]
  end
end

您可能需要先检查您是否没有覆盖预定义的方法(add_entry 方法顶部的 !@data.key?(key) && self.respond_to?(key) 可以),但那是另一个话题。如果有人试图添加一个名为 inspectclass 或,哦,例如 get_entry 的密钥,这可能会很糟糕!

IMO 这是一个非常糟糕的主意。不要这样做!您将增加复杂性,但收效甚微。

我推荐 OpenStruct。这些都是很棒的对象——您可以随意调用它们的 getter 和 setter,而无需提前指定属性。也许效率有点低,但这通常并不重要。

OpenStruct 的一个附带好处是您可以将属性分组到逻辑集中,例如connection_options、formatting_options 等。这里有一个示例脚本来说明:

#!/usr/bin/env ruby

require 'ostruct'

class MyClass

  attr_reader :config_options # only if you want to expose this

  def initialize
    @config_options = OpenStruct.new
  end

  def do_something
    config_options.color = 'yellow'
    config_options.size = 'medium'
  end

  def to_s
    config_options.to_h.to_s
  end
end

my_class = MyClass.new
my_class.do_something
puts my_class  # outputs: {:color=>"yellow", :size=>"medium"}