为什么使用extend / include而不是简单地在主对象中定义方法?

时间:2012-10-10 18:48:02

标签: ruby

RSpec添加了一个“describe”方法做顶级命名空间。但是,不是简单地在任何类/模块之外定义方法,而是执行此操作:

# code from rspec-core/lib/rspec/core/dsl.rb 
module RSpec
  module Core
    # Adds the `describe` method to the top-level namespace.
    module DSL
      def describe(*args, &example_group_block)
        RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register
      end
    end
  end
end

extend RSpec::Core::DSL
Module.send(:include, RSpec::Core::DSL)

使用这种技术有什么好处,而不是简单地在任何模块和类之外定义描述? (据我所知,在rspec-core的其他任何地方都没有使用DSL模块。)

2 个答案:

答案 0 :(得分:3)

我进行了此更改a few months ago,以便describe不再添加到系统中的每个对象。如果您在顶层定义它:

def describe(*args)
end

...然后系统中的每个对象都有一个私有的describe方法。 RSpec并不拥有系统中的每个对象,也不应该为每个对象添加describe。我们只希望在两个范围内提供describe方法:

describe MyClass do
end

(在顶层,离开主要对象)

module MyModule
  describe MyClass do
  end
end

(关闭任何模块,因此您将描述嵌套在模块范围内)

将它放在一个模块中可以很容易地扩展到主对象(仅将其添加到该对象,而不是每个对象)并将其包含在Module中(将其添加到所有模块中)。 / p>

答案 1 :(得分:1)

实际上,如果这就是代码中的所有内容,我真的不相信它会好得多 - 如果有的话。一个常见的争论是,您可以通过检查方法所有者来轻松检查RSpec是否负责在全局命名空间中添加此方法。不知何故,它从未觉得这是必要的,因为该方法的位置已经存储了该信息。

在任何范围之外定义方法将等同于在Object中定义私有实例方法:

class Object
  private
  def double(arg)
    arg * 2
  end
end

double(3)      # OK
3.double(3)    # Error: double is private
self.double(3) # Error: double is private

我认为私有性是一个有用的方面,因为它阻止了某些没有意义的方法调用,问题中显示的代码缺乏。

虽然在模块中定义方法是有好处的,但RSpec代码似乎没有使用它:使用module_function,不仅保留了实例方法的私有性,而且你还得到一个公共类方法。这意味着如果你有一个同名的实例方法,你仍然可以通过使用类方法版本来引用模块定义的方法。

module_function的常见示例是Kernel模块,其中包含大多数类似函数的核心方法,如puts(另一个是Math)。如果您在重新定义puts的班级中,如果需要,您仍然可以明确使用Kernel#puts

class LikeAnIO
  def puts(string)
    @output << string
  end

  def do_work
    puts "foo" # inserts "foo" in @output
    Kernel.puts "foo" # inserts "foo" in $stdout
  end
end