我想创建一个ruby模块,当它被一个类包含时,它设置一个具有默认值的类变量,该默认值可以在类定义中被覆盖。我一直在用元编程来扭曲自己,试图让它发挥作用,但我无法做到正确。
我想要的:
class A
include Nickname
end
A.nickname # => 'A'
A.new.nickname # => 'A'
class B
include Nickname
set_nickname "Bubba"
end
B.nickname # => 'Bubba'
B.new.nickname # => 'Bubba'
class BB < B
end
BB.nickname # => 'Bubba'
BB.new.nickname # => 'Bubba'
A.nickname # => 'A'
A.new.nickname # => 'A'
我试过
module Nickname
def self.included(klass)
klass.class_eval <<-EOS
@@nickname = "#{klass.name}"
def self.nickname
@@nickname
end
def nickname
@@nickname
end
def self.set_nickname(new_nick)
@@nickname = new_nick
end
EOS
end
end
但是在Nickname模块上设置了昵称,因此包含该模块的每个类都具有相同的昵称(最后设置的是什么)。 原来代码是对的。在7stud指出这一点之后,我回顾了我的工作。似乎因为我在irb
进行测试并且先前已经通过其他几种方式定义了Nickname
,所以我得到了不良行为。实际上,在irb
的新负载下,上述代码的工作方式与我想要的完全一样。对不起。
然后我尝试使用类实例变量
module Nickname
def self.included(klass)
klass.send(:define_singleton_method, :nickname) do
@nickname
end
klass.send(:define_method, :nickname) do
klass.nickname
end
klass.send(:define_singleton_method, :set_nickname) do |new_nick|
@nickname = new_nick
end
klass.set_nickname klass.name
end
end
这几乎有效,但它使用了类实例变量,因此BB.nickname
为nil
。
我在class_eval
内尝试了instance_eval
和self.included
的各种组合,我无法全部合作。如果我设法将值存储为包含类中的类变量,我就无法使类能够覆盖该值和/或我无法设置默认值。
故事的道德1,要小心在irb
中进行元编程。你失败的实验很可能会破坏你的下一次实验。
故事的道德2是,如果您认为类变量很奇怪,请查看具有继承和instance_variable_set/get
的类实例变量。当一个子类继承了超类&#39;类实例变量,该变量仍然附加到子类实例的超类部分,因此您无法通过子类中的@var
访问它,并且您无法从访问{{1的超类函数访问它但是,正如7stud所示,您可以从一个自己调用@var
的超类函数访问它。
答案 0 :(得分:2)
...但是在昵称模块上设置了昵称......
我没有看到:
module Nickname
def self.included(klass)
klass.class_eval %Q{
@@nickname = #{name}
def self.set_nickname(new_nick)
@@nickname = new_nick
end
}
end
end
class A
include Nickname
end
p Nickname.class_variables(false)
p A.class_variables(false)
A.set_nickname("Joe")
p Nickname.class_variables(false)
p A.class_variables(false)
--output:--
[]
[:@@nickname]
[]
[:@@nickname]
每个班级 包含该模块的将具有相同的昵称(无论如何 设置最后)。
不,include
不是罪魁祸首:
module Nickname
def self.included(includer)
includer.class_eval %Q{
@@nickname = #{name}
def self.set_nickname(new_nick)
@@nickname = new_nick
end
def self.nickname
@@nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B
include Nickname
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Joe"
相反,问题是inheritance
:
module Nickname
def self.included(includer)
includer.class_eval %Q{
@@nickname = #{name}
def self.set_nickname(new_nick)
@@nickname = new_nick
end
def self.nickname
@@nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B < A
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Sally"
继承层次结构中的所有类共享@@variable
,这是Sergio Tulentsev试图在评论中警告你的内容。你可能不想使用@@variable
(没人做)。这是一个class instance
变量,它不会遇到这个问题:
module Nickname
def self.included(includer)
includer.class_eval do
@nickname = includer.name #Instance variables attach themselves to whatever object is self, and class_eval sets self to the receiver, i.e. includer
define_method(:nickname) do #Using define_method() with a block makes the includer variable visible inside the nickname() method.
includer.instance_variable_get(:@nickname)
end
end
includer.singleton_class.class_eval do
define_method(:nickname) do
includer.instance_variable_get(:@nickname)
end
define_method(:set_nickname) do |new_val|
includer.instance_variable_set(:@nickname, new_val)
end
end
end
end
class A
include Nickname
end
p A.nickname
p A.new.nickname
class B
include Nickname
set_nickname "Bubba"
end
p B.nickname
p B.new.nickname
class BB < B
end
p BB.nickname
p BB.new.nickname
--output:--
"A"
"A"
"Bubba"
"Bubba"
"Bubba"
"Bubba"
然后你可以在BB中设置类实例变量,它不会影响B的类实例变量:
...
...
class BB < B
include Nickname
set_nickname "Bubba Bubba"
end
p BB.nickname
p BB.new.nickname
p B.nickname
p B.new.nickname
--output:--
...
...
"Bubba Bubba"
"Bubba Bubba"
"Bubba"
"Bubba"
答案 1 :(得分:0)
▶ module A
▷ def self.included base
▷ base.class_variable_set :@@nickname, 'mudasobwa'
▷ class << base
▷ def nickname # extended class method
▷ class_variable_get :@@nickname
▷ end
▷ end
▷ end
▷ end
▶ class B
▷ include A
▷ def nickname # instance method, might be put in `A`
▷ @@nickname
▷ end
▷ end
▶ B.nickname
#⇒ "mudasobwa"
▶ B.new.nickname
#⇒ "mudasobwa"
▶ B.class_variable_set :@@nickname, 'google'
#⇒ "google"
▶ B.nickname
#⇒ "google"
▶ B.new.nickname
#⇒ "google"
希望它有所帮助。