42 ruby inheritance metaprogramming class
情况:我有多个类,每个类应该包含一个带有配置哈希的变量; 每个类的不同哈希,但对于类的所有实例都是相同的.
起初,我试过这样的
class A
def self.init config
@@config = config
end
def config
@@config
end
end
class B < A; end
class C < A; end
Run Code Online (Sandbox Code Playgroud)
但很快就注意到它不会那样工作,因为@@ config是在A的上下文中保存,而不是B或C,因此:
B.init "bar"
p B.new.config # => "bar"
p C.new.config # => "bar" - which would be nil if B had it's own @@config
C.init "foo"
p B.new.config # => "foo" - which would still be "bar" if C had it's own @@config
p C.new.config # => "foo"
Run Code Online (Sandbox Code Playgroud)
我想过像这样使用它:
modules = [B, C]
modules.each do |m|
m.init(@config[m.name])
end
# ...
B.new # which should then have the correct config
Run Code Online (Sandbox Code Playgroud)
现在,我很清楚为什么会这样,但我不确定它是这样的原因.
它不能以其他方式工作,将类变量保存在子类的上下文中吗?
我也发现令人恼火的事实是,即使被称为"超级",自我也总是子类.由此,我首先期望来自超类的代码"在子类的上下文中执行".
对此的一些启示将不胜感激.
另一方面,我可能不得不接受它的工作方式,我必须找到另一种方法来做到这一点.
是否有"元"方式来做到这一点?(我试过class_variable_set等,但没有运气)
或者也许是'init'方法的整体想法首先存在缺陷,并且还有其他一些"模式"可以做到这一点?
我可以让@@ config一个哈希,拿着所有的配置并总是选择正确的一个,但我发现有点尴尬..(那里不是继承来解决这类问题吗?;)
Jör*_*tag 109
这@@variables
不是类变量.它们是类层次结构变量,即它们在整个类层次结构之间共享,包括所有子类和所有子类的所有实例.(有人认为人们应该@@variables
更多地考虑$$variables
,因为他们实际上有更多的共同点而$globals
不是@ivars
.这种方式更少混淆.其他人已经走得更远,并建议他们应该简单地从语言中删除.)
从某种意义上说,Ruby没有类变量,例如Java(它们被称为静态字段)具有它们.它不需要类变量,因为类也是对象,因此它们可以像任何其他对象一样拥有实例变量.你所要做的就是去除无关@
的东西.(并且您必须为类实例变量提供一个访问器方法.)
class A
def self.init config
@config = config
end
def self.config # This is needed for access from outside
@config
end
def config
self.class.config # this calls the above accessor on self's class
end
end
Run Code Online (Sandbox Code Playgroud)
让我们稍微简化一下,因为A.config
它显然只是一个attribute_reader:
class A
class << self
def init config
@config = config
end
attr_reader :config
end
def config
self.class.config
end
end
Run Code Online (Sandbox Code Playgroud)
而且,实际上,A.init
它只是一个有趣名字的作家,所以让我们将它重命名为A.config=
并使其成为一个作家,这反过来意味着我们的方法对现在只是一个访问者对.(由于我们更改了API,测试代码也必须改变,显然.)
class A
class << self
attr_accessor :config
end
def config
self.class.config
end
end
class B < A; end
class C < A; end
B.config = "bar"
p B.new.config # => "bar"
p C.new.config # => nil
C.config = "foo"
p B.new.config # => "bar"
p C.new.config # => "foo"
Run Code Online (Sandbox Code Playgroud)
但是,如果你需要的话,我无法摆脱这种设计更为根本的感觉.