问题
你定义了方法:
def add(c)
return self + c
end
并试图这样使用它:
3.add(4) #=> NoMethodError: private method `add' called for 3:Fixnum
了解此错误消息
此错误消息会准确告诉您问题所在。我认为您的问题只是您不了解 Ruby 如何调用对象上的方法。
当 Ruby 看到3.add(4) 时,它首先查看接收器,3,然后确定:
3.class #=> Fixnum
这告诉它add 方法的定义位置:在Fixnum 类中或Fixnum 的祖先类或模块之一中。
所以它在那里寻找它,没有找到它,并发出一条错误消息。我们可以确认它不存在:
Fixnum.instance_methods.include?(:add)
#=> false
那么add 是在哪里定义的?
不过,您确实定义了它,那么它在哪里?让我们找出答案:
method(:add).owner
#=> Object
Object.instance_methods.include?(:add)
#=> false
Object.instance_methods 返回在Object 和Object 的祖先上定义的所有public 实例方法的数组。 add 不在其中,因此我们得出结论 add 是一个受保护的或私有方法:
Object.protected_instance_methods.include?(:add)
#=> false
Object.private_instance_methods.include?(:add)
#=> true
让我们尝试在Object 的实例上调用该方法:
Object.new.add(4)
#=> NoMethodError:
# private method `add' called for #<Object:0x007fdb6a27fa68>
这是有道理的,考虑到Object#add 是私有的。但是,我们可以使用 Object#send 调用私有方法:
Object.new.send(:add,4)
#NoMethodError: undefined method `+' for #<Object:0x007fdb6a28e068>
作为练习,请确保您了解 Ruby 采取的导致她引发此异常的步骤(实例方法 + 没有在 Object 上定义,或者等效地,Object 的实例没有有一个方法+)。
顺便问一下,add 是在哪里定义的?我的意思是,当您定义 self 时,它的值是多少?让我们看看:
self #=> main
self.class #=> Object
我们看到add 必须在其接收者是实例的类上定义。 (一口,是的,但这很重要,所以请确保您理解这一点)。
为什么Object#add 是私有的而不是公共的?
考虑:
def greet
puts 'hi'
end
class A
end
A.private_instance_methods.include?(:add)
#=> true
A.new.send(:greet)
#=> 'hi'
这是因为A从Object继承greet:
A.ancestors.include?(Object) #=> true
如果Object#greet 是公共的,那么每个内置类和您定义的每个类都会有一个公共实例方法greet。那会带来很大的痛苦。 (假设你有一个方法great 并且输入错误greet!)即使是私有的greet 也可能会造成麻烦。)
应该在哪里定义add?
由于add.class => Fixnum,我们这样定义它:
class Fixnum
def add(other)
self + other
end
end
Fixnum.instance_methods.include?(:add) #=> true
3.add(4) #=> 7
如果我在class Fixnum 之后包含puts "self#{self}" 行,它将打印“Fixnum”。用显示self 值的puts 语句对代码进行加盐通常有助于理解发生了什么。
最后一件事:
method(:add).owner
#=> NameError: undefined method `add' for class `Object'
为什么这没有返回Fixnum?由于method 没有明确的接收者(即没有xx.method),Ruby 假定接收者是self,这里是:
self #=> main
所以她在self.class => Object 中寻找方法method,你知道她找到了什么(或者,我应该说,没有找到)。相反,我们需要写:
Fixnum.instance_method(:add).owner #=> Fixnum
或
3.method(:add).owner #=> Fixnum
这里的3 当然可以替换为Fixnum 的任何实例。
注意我已经稍微简化了这个解释。在搜索方法时,Ruby 还会查看接收者的单例类。但是,对于直接对象(数字、符号、true、false 和 nil)来说,这不是问题,因为它们没有单例类:
3.singleton_class #=> TypeError: can't define singleton
相比之下,例如:
[1,2].singleton_class #=> #<Class:#<Array:0x007fbcf18c01a8>>