【问题标题】:Ruby dynamically create methods and variablesRuby 动态创建方法和变量
【发布时间】:2016-08-31 03:14:58
【问题描述】:

如何用更合理的方法替换 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

【问题讨论】:

    标签: ruby variables dynamic methods instance


    【解决方案1】:

    我不确定你所说的“更明智”,但这里是没有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)
    

    【讨论】:

      【解决方案2】:

      这可以使用instance_variable_setattr_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,而不是为每个变量定义一个新的实例方法。

      【讨论】:

        【解决方案3】:

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

        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) &amp;&amp; self.respond_to?(key) 可以),但这是另一个对话。例如,如果有人试图添加一个名为 inspectclassget_entry 的密钥,那可能会很糟糕!

        【讨论】:

        • 危险!它必须是Hash.new{|h, k| h[k] = []},否则所有新键都会获得与其他键相关的值。您传递的 [] 对象是内存中的单个对象,将被所有键引用,我们使用的 &lt;&lt; 运算符会修改该对象。试试看! h = Hash.new([]); h[:x] &lt;&lt; 1; h[:y] 给你[1]。我通常不使用 Hash 的块初始化器,除非它比保存 ||=
        • 你做到了!对不起,我瞎了。但更糟糕的是,没有h, k 参数,因为您根本没有将它存储在哈希对象中! h = Hash.new{[]}; h[:x] &lt;&lt; 1; h[:x][]
        • 我认为危险仍然存在,有人将花括号与普通括号混淆,并且问题一直存在
        • 来自docs “如果需要,将值存储在哈希中是块的责任。”
        • 不需要您的第一行:“编辑:...”。这是不必要的干扰。我还将启动到“IMO”。答案不应该是意见。
        【解决方案4】:

        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"}
        

        【讨论】:

        • @mudasobwa 上下文/详细说明,好吗?
        • 我只是想删除一个指向伟大图书馆的链接,即 [恕我直言] 绝对是 OpenStruct 的更好等价物。
        猜你喜欢
        • 2013-12-21
        • 2022-12-13
        • 2013-03-29
        • 2011-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多