给定一个类,判断实例是否有该方法(Ruby)

252
我知道在Ruby中可以使用respond_to?来检查对象是否有某个特定的方法。
但是,针对一个类,我如何检查实例是否有某个特定的方法?
比如说,像这样:
Foo.new.respond_to?(:bar)

不过我觉得肯定有比实例化新实例更好的方法。

12个回答

394

我不知道为什么每个人都建议你使用instance_methodsinclude?,而method_defined?就可以完成任务。

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

注意

如果你是从另一种面向对象语言转到 Ruby,或者你认为 method_defined 只是指你使用以下方式显式定义的方法:

def my_method
end

那么请阅读以下内容:

在 Ruby 中,模型上的属性(attribute)本质上也是一个方法。所以 method_defined? 不仅会对方法返回 true,也会对属性返回 true。

例如:

给定一个拥有字符串属性 first_name 的类的实例:

<instance>.first_name.class #=> String

<instance>.class.method_defined?(:first_name) #=> true

因为first_name既是属性又是方法(并且是字符串类型的String)。


9
method_defined? 是最好的解决方案,因为在 Ruby 1.8 和 1.9 之间 instance_methods 发生了变化。1.8 返回一个字符串数组,而 1.9 返回一个符号数组。method_defined? 在 1.8 和 1.9 中都接受符号作为参数。 - Jason
2
更不用说,每次调用 :instance_methods 都会创建一个新的数组,然后 :include? 就必须遍历这个数组来查找。相比之下,:method_defined? 会在内部查询方法查找表(一个 C 语言哈希表查找),并且不会创建任何新对象,直接告诉你结果。 - Asher
4
当你使用 String.instance_methods(false).include? :freeze 这种方式时,虽然有至少一个优点,即参数中的 false 可以准确判断 String 类中是否定义了 freeze 方法。在这种情况下,它会返回 false,因为 freeze 方法是通过 String 继承自 Kernel 模块的,但是 String.method_defined? :freeze 始终会返回 true,对于这个目的没什么帮助。 - ugoa
1
@Bane,你混淆了类上的方法和实例可用的方法。method_defined? 必须在类上使用(例如 Array),但你试图在实例上使用它(例如 [])。 - horseyguy
@banister 完全正确。谢谢您的澄清,读起来非常合理。 :) 我已经删除了我的评论,以免造成混淆。 - Bane
显示剩余2条评论

46

您可以如下使用method_defined?

String.method_defined? :upcase # => true

和其他人提出的instance_methods.include?相比,这种方法更容易、便携和高效。

请记住,如果一个类动态地响应某些调用,例如通过重新定义respond_to?或自Ruby 1.9.2以来通过定义respond_to_missing?,那么你将不会知道。


3
注意,如果您手头有一个实例对象并且不知道它所属的类,您可以执行instance.class.method_defined? :upcase - user483040

29

实际上,这种方法对于对象和类都不起作用。

而这种方法可以:

class TestClass
  def methodName
  end
end

所以根据提供的答案,这个方法可以正常工作:

TestClass.method_defined? :methodName # => TRUE

但是这样做是不起作用的:

t = TestClass.new
t.method_defined? : methodName  # => ERROR!

所以我同时将其用于类和对象:
类:

TestClass.methods.include? 'methodName'  # => TRUE

对象:

t = TestClass.new
t.methods.include? 'methodName'  # => TRUE

3
例如,您还可以使用`instance.class.method_defined?'的语法。 - Luksurious
这取决于你使用的 Ruby 版本。上面的方法适用于所有版本,包括 1.8.7。 - Alex V
@Luksurious:如果您使用了mixin,我认为那不会起作用。 - user1142217

10

对于 "给定一个类,查看实例是否有方法(Ruby)" 的答案更好。显然 Ruby 内置了这个功能,但我不知道。无论如何,我的答案仅供参考。

Ruby 类响应 instance_methodspublic_instance_methods 方法。在 Ruby 1.8 中,第一个方法会将所有实例方法名称列在一个字符串数组中,而第二个方法则将其限制为公共方法。最可能想要的是第二种行为,因为 respond_to? 默认也会将自己限制为公共方法。

Foo.public_instance_methods.include?('bar')

在Ruby 1.9中,这些方法返回符号数组。
Foo.public_instance_methods.include?(:bar)

如果你计划经常这样做,你可能想扩展Module以包括一种快捷方法。(将此分配给Module而不是Class可能会看起来有些奇怪,但由于这是instance_methods方法存在的位置,最好保持与该模式一致。)
class Module
  def instance_respond_to?(method_name)
    public_instance_methods.include?(method_name)
  end
end

如果你想支持Ruby 1.8和Ruby 1.9,那么在这里添加搜索字符串和符号的逻辑将非常方便。


1
method_defined? 更好 :) - horseyguy
@banister - 哦,我的天啊,我居然错过了! xD 在 @Marc 的答案后面加了一个 +1,并添加了一个链接以确保不会被忽略 :) - Matchu

5
尝试使用Foo.instance_methods.include? :bar来检查。

3
class Foo
 def self.fclass_method
 end
 def finstance_method
 end
end

foo_obj = Foo.new
foo_obj.class.methods(false)
=> [:fclass_method] 

foo_obj.class.instance_methods(false)
=> [:fclass_method] 

希望这能对您有所帮助!

3

如果你想检查一个对象是否能够响应一系列的方法,你可以这样做:

methods = [:valid?, :chase, :test]

def has_methods?(something, methods)
  methods & something.methods == methods
end

方法 & something.methods 将两个具有共同/匹配元素的数组连接起来。something.methods 包含您要检查的所有方法,它将等于 methods。例如:

[1,2] & [1,2,3,4,5]
==> [1,2]

so

[1,2] & [1,2,3,4,5] == [1,2]
==> true

在这种情况下,您需要使用符号,因为当您调用.methods时,它会返回一个符号数组,如果您使用了["my", "methods"],它将返回false。

3

3

不确定这是否是最好的方法,但您始终可以尝试以下操作:

Foo.instance_methods.include? 'bar'

2
根据Ruby版本不同,使用

klass.instance_methods.include :method_name"method_name"

来检查是否包含方法名。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接