Ruby子类化与类变量问题:子类类方法设置的类变量为何作用于父类?
我太懂你这种困惑了!当初我刚接触Ruby类变量的时候也踩过一模一样的大坑😅
问题出在哪?Ruby类变量的“共享特性”
你用的@@can_say是Ruby的类变量,但它的特性是在整个继承链里共享——也就是说,父类Obj和所有子类(比如Dog、你没写完的Cat)共用同一个数组实例。所以你在Dog里调用says方法往数组里加元素,其实是直接修改了父类Obj里的那个数组,完全不是你预期的“子类独有”。
怎么解决?用类实例变量替代类变量
每个Ruby类本身就是Class类的实例,所以我们可以用类级别的实例变量(@开头)来替代@@类变量,这样每个子类都会拥有独立的变量实例。修改你的代码如下:
class Obj # 当有子类继承时,自动给子类初始化独立的@can_say数组 def self.inherited(subclass) subclass.instance_variable_set(:@can_say, []) end def self.says(word) @can_say.push(word) end def self.listens_to(calling) define_method(calling) do # 调用当前类的类方法获取专属的@can_say "I say #{self.class.can_say}" end end # 新增类方法暴露@can_say,供实例方法调用 def self.can_say @can_say end end class Dog < Obj says "woof" says "bark bark" listens_to "goodboy" end class Cat < Obj says "meow" says "purr" listens_to "kitty" end # 测试看看效果: puts Dog.new.goodboy # 输出:I say ["woof", "bark bark"] puts Cat.new.kitty # 输出:I say ["meow", "purr"]
这里的核心是self.inherited钩子:当子类继承Obj时,这个方法会自动触发,我们给每个子类单独初始化一个@can_say数组,这样各个子类的变量就完全独立,不会互相干扰了。
如果你用Rails,还有更简单的方案
要是你在Rails项目里写代码,直接用Rails提供的class_attribute方法就行——它会自动帮你处理子类独立属性的问题,不用手动写inherited钩子:
class Obj # 定义类属性,默认值是空数组,子类会自动拥有独立副本 class_attribute :can_say, default: [] def self.says(word) can_say.push(word) end def self.listens_to(calling) define_method(calling) do "I say #{self.class.can_say}" end end end class Dog < Obj says "woof" says "bark bark" listens_to "goodboy" end class Cat < Obj says "meow" listens_to "kitty" end # 测试结果和之前一致 puts Dog.new.goodboy # I say ["woof", "bark bark"] puts Cat.new.kitty # I say ["meow"]
最后再敲个重点
以后别再用@@类变量来实现子类独立的状态啦!它们是继承链共享的,很容易踩坑。纯Ruby环境就用「类实例变量+inherited钩子」,Rails环境直接用class_attribute,完美满足你“子类变量仅属于子类”的需求~
内容的提问来源于stack exchange,提问作者599644
相关产品推荐
相关产品推荐

