Ruby中为何Cat.new.name可用而Dog.new.name不可用?单例对象attr_accessor异常
哈哈,这个坑我刚学Ruby的时候也踩过!核心就是class_eval和instance_eval的作用上下文完全不一样,咱们一步步掰扯清楚。
首先先把大家默认的那两段示例代码贴出来(毕竟你提到了两种实现):
# Cat的实现 class Cat class_eval do attr_accessor :name end end # Dog的实现 class Dog instance_eval do attr_accessor :name end end
先搞懂attr_accessor到底干了啥
attr_accessor :name本质是帮我们自动生成两个实例方法:
def name @name end def name=(value) @name = value end
重点是:它生成的方法默认会挂在当前执行上下文对应的“实例方法集合”里——至于这个集合是类的实例的,还是类对象自己的,全看你用的是class_eval还是instance_eval。
class_eval vs instance_eval的核心差异
对于Cat的class_eval
class_eval(也叫module_eval)是在类本身的上下文里执行代码,此时self就是Cat类,但它的作用是修改这个类的实例方法定义。
所以在class_eval里调用attr_accessor :name,相当于给Cat的所有实例都添加上了name和name=方法,自然Cat.new.name能正常调用。
对于Dog的instance_eval
instance_eval是在调用者的单例对象上下文里执行代码——这里的调用者是Dog这个类对象本身,所以它会打开Dog的单例类来定义方法。
这时候调用attr_accessor :name,生成的name和name=方法是属于Dog这个类对象自己的单例方法,而不是Dog实例的方法。这就是为什么Dog.name能用(直接调用类对象的方法),但Dog.new.name会报错——因为Dog的实例根本没有这个方法。
结合Ruby文档的补充说明
Ruby官方文档明确提到:instance_eval会将self切换为调用该方法的对象,并且所有在其中定义的方法都会成为该对象的单例方法;而class_eval则是打开模块/类本身,用于定义该类所有实例共享的方法,或者修改类的属性。
简单总结:
- 要给类的所有实例加方法:用
class_eval(或者直接在类里写代码,效果一样) - 要给类对象自己加专属方法:用
instance_eval
关于“单例对象上attr_accessor不符合预期”的问题
你遇到的这个情况,本质就是把attr_accessor用在了单例类上——它确实会生成方法,但这些方法只属于那个单例对象(也就是Dog类本身),和类的实例完全没关系。如果想让Dog.new.name正常工作,把instance_eval换成class_eval就搞定了。
内容的提问来源于stack exchange,提问作者Neil

