【问题标题】:How to judge whether a method has defined in a class?如何判断一个方法是否已经定义在一个类中?
【发布时间】:2011-03-25 02:22:08
【问题描述】:
class C1
  unless method_defined? :hello  # Certainly, it's not correct. I am asking to find something to do this work.
    def_method(:hello) do
      puts 'Hi Everyone'
    end
  end
end

那么,如何判断一个方法是否定义了呢?

【问题讨论】:

    标签: ruby-on-rails ruby methods defined


    【解决方案1】:

    您发布的代码可以很好地检查方法是否已定义。 Module#method_defined? 正是正确的选择。 (还有变体Module#public_method_defined?Module#protected_method_defined?Module#private_method_defined?。)问题在于您对def_method 的调用,它不存在。 (称为Module#define_method)。

    这就像一个魅力:

    class C1      
      define_method(:hello) do
        puts 'Hi Everyone'
      end unless method_defined? :hello
    end
    

    但是,由于您已经提前知道名称并且不使用任何闭包,因此无需使用Module#define_method,您可以使用def 关键字代替:

    class C1
      def hello
        puts 'Hi Everyone'
      end unless method_defined? :hello
    end
    

    还是我误解了你的问题,你担心继承?在这种情况下,Module#method_defined? 不是正确的选择,因为它遍历了整个继承链。在这种情况下,您将不得不使用Module#instance_methods 或其表亲之一Module#public_instance_methodsModule#protected_instance_methodsModule#private_instance_methods,它们带有一个可选参数,告诉它们是否包含来自超类/mixins 的方法。 (请注意,文档是错误的:如果您不传递任何参数,它包含所有继承的方法。)

    class C1
      unless instance_methods(false).include? :hello
        def hello
          puts 'Hi Everyone'
        end
      end
    end
    

    这是一个小测试套件,表明我的建议有效:

    require 'test/unit'
    class TestDefineMethodConditionally < Test::Unit::TestCase
      def setup
        @c1 = Class.new do
          def self.add_hello(who)
            define_method(:hello) do
              who
            end unless method_defined? :hello
          end
        end
    
        @o = @c1.new
      end
    
      def test_that_the_method_doesnt_exist_when_it_hasnt_been_defined_yet
        assert !@c1.method_defined?(:hello)
        assert !@c1.instance_methods.include?(:hello)
        assert !@o.methods.include?(:hello)
        assert !@o.respond_to?(:hello)
        assert_raise(NoMethodError) { @o.hello }
      end
    
      def test_that_the_method_does_exist_after_it_has_been_defined
        @c1.add_hello 'one'
    
        assert @c1.method_defined?(:hello)
        assert @c1.instance_methods.include?(:hello)
        assert @o.methods.include?(:hello)
        assert_respond_to @o, :hello
        assert_nothing_raised { @o.hello }
        assert_equal 'one', @o.hello
      end
    
      def test_that_the_method_cannot_be_redefined
        @c1.add_hello 'one'
    
        assert @c1.method_defined?(:hello)
        assert @c1.instance_methods.include?(:hello)
        assert @o.methods.include?(:hello)
        assert_respond_to @o, :hello
        assert_nothing_raised { @o.hello }
        assert_equal 'one', @o.hello
    
        @c1.add_hello 'two'
    
        assert @c1.method_defined?(:hello)
        assert @c1.instance_methods.include?(:hello)
        assert @o.methods.include?(:hello)
        assert_respond_to @o, :hello
        assert_nothing_raised { @o.hello }
        assert_equal 'one', @o.hello, 'it should *still* respond with "one"!'
      end
    end
    

    【讨论】:

    • 测试在 1.9.2 中通过,但 test_that_the_method_cannot_be_redefinedtest_that_the_method_does_exist_after_it_has_been_defined 在 ruby​​ 1.8.7 下失败。
    【解决方案2】:

    查看Ruby Object class。它有一个methods 函数来获取方法列表和一个respond_to? 来检查特定方法。所以你想要这样的代码:

    class C1
      def add_hello
        unless self.respond_to? "hello"
          def hello
            puts 'Hi Everyone'
          end
        end  
      end
    end
    
    cone.hello      #This would fail
    cone.add_hello  
    cone.hello      #This would work
    

    【讨论】:

    • -1,有 4 个原因:1) 它没有解决 OPs 问题。使用method_defined? 就好了,问题是他拼错了define_method。 2) respond_to? 不检查特定方法,它检查对象是否响应特定消息。 (提示:这个名字有点暴露了,你不觉得吗?)理解方法和消息之间的区别是理解 Ruby 甚至一般的 OO 的基础。 3) 在您的代码中,检查类对象C1 是否响应:hello,并在此基础上为C1instances 定义hello 方法。 ...
    • ... 再次重申:理解 instancesclasses 之间的区别是理解 Ruby 和一般基于类的 OO 的基础。 4)您的测试套件实际上并没有测试OP关心的东西,即您不能两次定义该方法。你只测试你可以定义一次方法,但这不是问题。
    • @jorg,他将respond_to? 放在实例方法(add_hello)中,因此它确实检查了实例(而不是类)。另外,出于好奇,在 Ruby 中发送消息和调用方法有什么区别? :)
    • @jorg,关于您的第 2 点:在 ruby​​ 中,方法是通过传递消息来调用的,这是理解 ruby​​ 的基础
    • @jorg。但总的来说,我同意我错过了理解这个问题。我从来没有做过这样的事情,但从文档中看到这是可能的,所以我很快就找到了解决方案。
    【解决方案3】:

    Object 类有方法“methods”:docs

     class Klass
       def kMethod()
       end
     end
     k = Klass.new
     k.methods[0..9]    #=> ["kMethod", "freeze", "nil?", "is_a?",
                        #    "class", "instance_variable_set",
                        #    "methods", "extend", "__send__", "instance_eval"]
     k.methods.length   #=> 42
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-28
      • 1970-01-01
      • 1970-01-01
      • 2017-09-05
      • 1970-01-01
      • 2020-07-21
      • 1970-01-01
      相关资源
      最近更新 更多