我有一些类似的代码:
class Country
attr_reader :name
def initialize
@name = "MyName".freeze
end
def government
@government ||= Government.new(self)
end
def symbols
@symbols ||= Symbols.new(self)
end
def economy
@economy ||= Economy.new(self)
end
def education
@education ||= Education.new(self)
end
def healthcare
@healthcare ||= Healthcare.new(self)
end
def holidays
@holidays ||= Holidays.new(self)
end
def religion
@religion ||= Religion.new(self)
end
end
如何动态创建方法?我试过了:
class Country
attr_reader :name
COMPONENETS = %w(government symbols economy education healthcare holidays religion)
COMPONENETS.each do |m|
define_method(m) do |argument|
instance_variable_set("@#{m}",Object.const_get(m.capitalize).new(self))
end
end
def initialize
@name = "MyName".freeze
end
end
如果我尝试:
puts Country.new.education.inspect
我收到以下错误:
country.rb:16:in `block (2 levels) in <class:Country>': wrong number of arguments (0 for 1) (ArgumentError)
from country.rb:27:in `<main>'
我在这里缺少什么?
答案 0 :(得分:2)
在原始代码中,您定义了所有不带参数的方法:
def education
# ^^^
@education ||= Education.new(self)
end
在元编程代码中,您定义所有方法以采用名为argument
的单个参数:
define_method(m) do |argument|
# ^^^^^^^^^^
instance_variable_set("@#{m}", Object.const_get(m.capitalize).new(self))
end
但是,你用零参数调用它:
puts Country.new.education.inspect
# ^^^
显然,你的方法是懒惰的getter,所以他们不应该参数:
define_method(m) do
instance_variable_set("@#{m}", Object.const_get(m.capitalize).new(self))
end
请注意,您的代码还有其他问题。在原始代码中,如果实例变量未定义nil
或false
,则使用条件赋值仅执行赋值,而在元编程代码中,您始终无条件地设置它。它应该是更像这样的东西:
define_method(m) do
if instance_variable_defined?(:"@#{m}")
instance_variable_get(:"@#{m}")
else
instance_variable_set(:"@#{m}", const_get(m.capitalize).new(self))
end
end
注意:我还从Object.
的调用中删除了const_get
,以使用常规常量查找规则查找常量(即,首先在词典中向外向上然后在继承层次结构中),因为这对应如何查找原始代码段中的常量。
这并不完全等同于您的代码,因为它仅在未定义时才设置实例变量,而不是在false
或nil
时设置实例变量,但我想这更接近您的意图反正。
我会封装此代码以使其意图更清晰:
class Module
def lazy_attr_reader(name, default=(no_default = true), &block)
define_method(name) do
if instance_variable_defined?(:"@#{name}")
instance_variable_get(:"@#{name}")
else
instance_variable_set(:"@#{name}",
if no_default then block.(name) else default end)
end
end
end
end
class Country
attr_reader :name
COMPONENTS = %w(government symbols economy education healthcare holidays religion)
COMPONENTS.each do |m|
lazy_attr_reader(m) do |name|
const_get(name.capitalize).new(self))
end
end
def initialize
@name = 'MyName'.freeze
end
end
这样,有人在阅读你的Country
课程时不会去哼哼,所以有这个循环定义了有时获取并有时设置实例变量的方法#34; &#34;啊,这是一个创造懒惰吸气剂的循环!&#34;
答案 1 :(得分:1)
我猜你不需要argument
:
class Country
attr_reader :name
COMPONENETS = %w(government symbols economy education healthcare holidays religion)
COMPONENETS.each do |m|
define_method(m) do
instance_variable_set("@#{m}",Object.const_get(m.capitalize).new(self))
end
end
def initialize
@name = "MyName".freeze
end
end
答案 2 :(得分:1)
您只需使用eval:
class Country
attr_reader :name
COMPONENETS = %w(government symbols economy education healthcare holidays religion)
COMPONENETS.each do |m|
eval <<-DEFINE_METHOD
def #{m}
@#{m} ||= #{m.capitalize}.new(self)
end
DEFINE_METHOD
end
def initialize
@name = "MyName".freeze
end
end