ruby 模块如何对包含它的 类 执行条件?

How can a ruby module enforce conditions on classes in which it is included?

我如何编写一个 ruby 模块,对包含它的 classes 施加一些条件,这些条件必须满足当前打开的 class 定义的结尾?

具体来说,假设条件是"a class variable @@foo should be defined to be >0 "

我想写一个看起来像这样的模块:

module NeedPositiveFoo
  module ClassMethods
    def validate_positive_foo
      raise unless defined?(@@foo) && @@foo > 0
    end
  end

  def included(other)
    other.extend(ClassMethods)
  end
end

那么这个 class 定义将是有效的:

class ValidClass
  include NeedPositiveFoo
  @@foo = 3
end

但是这些 class 定义会在 end 结束后出现:

class InvalidClass1
  include NeedPositiveFoo
  # @@foo is not defined
end

class InvalidClass2
  include NeedPositiveFoo
  @@foo = -2
end

你不能挂钩 class 定义的末尾,因为没有一个 - class 可以在不同的文件、不同的时间、甚至不同的库中声明.

您可以做的是检查条件 包含模块时 ,并在定义的末尾声明包含:

module NeedPositiveFoo
  def included(other)
    raise unless defined?(@@foo) && @@foo > 0
  end
end

class ValidClass
  @@foo = 3
  include NeedPositiveFoo
end

class InvalidClass1
  # @@foo is not defined
  include NeedPositiveFoo
end

class InvalidClass2
  @@foo = -2
  include NeedPositiveFoo
end

class InvalidClass3
  include NeedPositiveFoo

  @@foo = 4 # declared after inclusion - not a valid state...
end

虽然 Uri Agassi 的答案在您被允许将 includes 放在 class 定义的最后时完美地工作,但下面的代码将工作,尽管放置了 include

def included(other)
  TracePoint.new(:end) do |tp|
    if tp.self == other
      tp.disable
      raise unless defined?(other.class_variable_get(:@@foo)) # checks
    end
  end.enable
  other.extend(ClassMethods)
end

TracePoint documentation.

问这样的问题时,看看 Ruby 核心库是如何做的通常很有用。 Ruby 核心库中有两个 well-known mixins,它们在 classes 上设置了某些条件,它们被混合到:

  • Enumerable 要求 class 有一个 each 方法,可以用零参数调用并接受一个块,它 yield 所有元素collection 连续。
  • Comparable 要求 class 有一个 <=> 方法,该方法可以用单个参数调用并用 -10、或 1,取决于参数是大于、等于还是小于接收者。

在这两种情况下,需求只是在文档中说明,而不是在代码中说明。由 class 作者来确保他们得到满足。

事实上,在 Enumerable 情况下,根本没有真正说明要求 ,只是假设任何有能力的 Ruby 程序员都知道他们。

我会遵循核心库作者设定的这种风格,因为这是 Ruby 支持者所习惯的。