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
我不确定你所谓的“更明智”是什么,但这是没有 eval
s 开头的模板:
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_method
和 define_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)
可以),但那是另一个话题。如果有人试图添加一个名为 inspect
、class
或,哦,例如 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"}
如何用更明智的方法替换 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
我不确定你所谓的“更明智”是什么,但这是没有 eval
s 开头的模板:
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_method
和 define_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)
可以),但那是另一个话题。如果有人试图添加一个名为 inspect
、class
或,哦,例如 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"}