Ruby mixins 和调用超级方法

Posted

技术标签:

【中文标题】Ruby mixins 和调用超级方法【英文标题】:Ruby mixins and calling super methods 【发布时间】:2010-09-05 20:45:29 【问题描述】:

好的,所以我一直在我的小 Rails 应用程序中重构我的代码,以努力消除重复,总体上让我的生活更轻松(因为我喜欢轻松的生活)。此重构的一部分是将我的两个模型共有的代码移动到我可以包含在我需要的地方的模块中。

到目前为止,一切都很好。看起来它会成功,但我刚刚遇到了一个我不知道如何解决的问题。该模块(我称之为可发送的)只是处理传真、电子邮件或打印文档 PDF 的代码。例如,我有一个采购订单,我有内部销售订单(想象中缩写为 ISO)。

我遇到的问题是,我想在加载对象后初始化一些变量(为拼写不正确的人初始化 :P ),所以我一直在使用 after_initialize 钩子。没问题...直到我开始添加更多的 mixin。

我遇到的问题是,我的任何一个 mixin 中都可以有一个 after_initialize,所以我需要在开始时包含一个 super 调用确保调用其他 mixin after_initialize 调用。太好了,直到我最终调用 super 并收到错误,因为没有 super 可调用。

这里有一个小例子,以防我不够混淆:

class Iso < ActiveRecord::Base
  include Shared::TracksSerialNumberExtension
  include Shared::OrderLines
  extend  Shared::Filtered
  include Sendable::Model

  validates_presence_of   :customer
  validates_associated    :lines

  owned_by                :customer
  order_lines             :despatched # Mixin

  tracks_serial_numbers   :items  # Mixin

  sendable :customer                      # Mixin

  attr_accessor :address

  def initialize( params = nil )
    super
    self.created_at ||= Time.now.to_date
  end
end

那么,如果每个 mixin 都有一个 after_initialize 调用,以及一个 super 调用,我该如何阻止最后一个 super 调用引发错误?如何在调用之前测试超级方法是否存在?

【问题讨论】:

【参考方案1】:

你可以用这个:

super if defined?(super)

这是一个例子:

class A
end

class B < A
  def t
    super if defined?(super)
    puts "Hi from B"
  end
end

B.new.t

【讨论】:

【参考方案2】:

你试过alias_method_chain吗?您基本上可以将所有after_initialize 电话联系起来。它就像一个装饰器:每个新方法都会添加一个新的功能层,并将控制权传递给“覆盖”的方法来完成其余的工作。

【讨论】:

【参考方案3】:

包含类(继承自ActiveRecord::Base,在本例中为Iso可以定义自己的after_initialize,所以除了alias_method_chain之外的任何解决方案(或保存原始文件的其他别名)有覆盖代码的风险。 @Orion Edwards 的解决方案是我能想到的最好的解决方案。还有其他的,但它们更骇人听闻。

alias_method_chain 还具有创建 after_initialize 方法的命名版本的好处,这意味着您可以在重要的极少数情况下自定义调用顺序。否则,您将受包含类包含 mixins 的任何顺序的支配。

稍后

我已经向 ruby​​-on-rails-core 邮件列表发布了一个关于创建所有回调的默认空实现的问题。无论如何,保存过程都会检查它们,所以我不明白为什么它们不应该在那里。唯一的缺点是创建额外的空堆栈帧,但这在每个已知实现中都相当便宜。

【讨论】:

【参考方案4】:

你可以在里面抛出一个快速的条件:

super if respond_to?('super')

你应该没问题 - 不要添加无用的方法;干净整洁。

【讨论】:

这确实似乎工作。 respond_to?('super') 将始终返回 false。【参考方案5】:

不用检查超级方法是否存在,你可以定义它

class ActiveRecord::Base
    def after_initialize
    end
end

这在我的测试中有效,不应破坏您现有的任何代码,因为定义它的所有其他类无论如何都会默默地覆盖此方法

【讨论】:

投反对票,因为它不是一个通用的解决方案。关键是你不知道超级是否存在,所以猴子补丁 ActiveRecord::Base#after_initialize 存在,你创建一个点,如果/当 ActiveRecord 添加一个 Base#after_initialize,或者它的 arity 改变时,你的代码将中断;如果已定义,最好有条件地调用它。 @yaauie - 当然,我本可以在猴子补丁之前添加一个raise 'oh no' if methods.include?(:after_initialize),但这会使示例更难理解……很容易陷入对所有边缘情况的详细说明这里的实际课程(只需修补一个基本方法)会迷失在噪音中。 monkeypatching 不是一个通用的解决方案,通常不应该鼓励。涉及可能工作的一次性 monekypatch 的答案会导致不必要的复杂和脆弱的代码,特别是如果您无法控制整个继承链。

以上是关于Ruby mixins 和调用超级方法的主要内容,如果未能解决你的问题,请参考以下文章

如何覆盖私有方法并快速调用超级?

如何调用 Oracle PL/SQL 对象超级方法

72.『Ruby美食』茄汁炖牛肉~~超级下饭的家常菜

如何从超级类的关联类中调用子类方法?

从子视图调用超级视图函数

调用超级超类的方法