【问题标题】:Detecting Ruby mixin being included检测是否包含 Ruby mixin
【发布时间】:2012-11-10 23:44:44
【问题描述】:

我有一个类似于this one 的问题。我正在编写一个具有插件系统的应用程序。有一个 Addon mixin 模块,它会检测何时包含并自动注册新的插件:

module Addon
  def self.included(receiver)
     addon = receiver.new   # Create an instance of the addon
     (snip)                 # Other stuff to register the addon 
     addon.on_register      # Tell the instance it was registered
  end
end

这是一个如何使用 mixin 的示例:

class MyAddon
  def on_register
    puts "My addon was registered"
  end
  include Addon  # ** note that this is at the end of the class **
end

如上所述,此实现要求包含位于类的底部。否则 on_register 不会在 self.included 被调用时定义。

我担心插件开发人员可能会不小心将包含放在顶部,导致插件无法工作。或者可能有一个派生类或在 MyAddon 类被包含后会扩展它的东西。

有没有更好的方法来解决这个问题?

【问题讨论】:

    标签: ruby dynamic


    【解决方案1】:

    在提炼出我找到的其他答案和其他一些信息后,我想记录最终对我有用的答案。

    正如this question 解释的那样,您无法在 include() 时间检测到何时“完成”了一个类的定义。因此,依靠“包含”回调来创建对象并不是一个非常可靠的解决方案。

    解决方案是发现所有插件并在所有内容加载后实例化它们。对插件开发者的唯一限制是他们的代码必须共享一个通用的顶级模块命名空间。

    我仍然不知道这是否是最好的方式,但它肯定比我开始的方式更好。

    这是在模块中搜索插件的代码。它从传入的命名空间开始并递归搜索包含插件类的类:

    def find_and_instantiate(mod, addons)
       consts = mod.constants
       consts.each do |c|
         sym = mod.const_get(c)
         if (sym.class == Module)
           find_and_instantiate(sym, addons)
         else
           if (sym.class == Class && sym.include?(Addon))
             addons << sym.new(@container)            
           end
         end        
       end
     end
    

    【讨论】:

    • 很高兴您找到了解决方案,并且您了解included 挂钩中的实例化并不是一个非常强大的解决方案。很高兴我给你 +1,虽然在 SO 上,回答自己的问题通常主要由新手完成。
    • 感谢您的 +1 和您的意见。我没有遵循Jeff Atwood 的建议:“......不仅可以提出和回答您自己的问题,而且明确鼓励。”当然,那是从去年开始的,也许从那以后情况发生了变化。
    • 这两个命题并不相互排斥。
    • @BorisStitnicky,你的语气在这里有点咄咄逼人......我认为回答你自己的问题是完全合法的,特别是在你正在处理答案不是的极端案例的情况下很容易谷歌搜索。
    【解决方案2】:

    我能想到的最好办法是在声明 #on_register 方法后通知模块的用户需要包含它:

    module Addon
      def self.included(receiver)
            raise "include #{self.name} after the #on_register method is defined" unless receiver.method_defined? :on_register
            receiver.new.send(:on_register)
      end
    end
    

    这不太理想,但它可以防止加重错误,直到您找到更好的方法。

    【讨论】:

      【解决方案3】:

      对不起,我必须把这个交给你。所以你想听听你的代码很聪明,伙计。是的,是的,它太聪明了。但是如果你想关心其他开发者,你就必须停止聪明,开始变得正常。

      Ruby 是一种面向对象的语言。 OOP 的基础是我首先定义我的对象结构和它们的方法,然后,当一切都定义好后,我理想地用一个调用 MyApp.new 来实例化我的世界。这样,我首先定义的内容并不重要。 'include' 调用用于建立模块继承层次结构,仅此而已。

      但是不,你不想做 OOP。您想滥用included 钩子并在其中实例化接收器(!),并调用其实例方法。这就是他们所说的混淆代码。换句话说,你从 OOP 回到了旧的 goto 风格的指令编程。 Ruby 允许你混淆,不是限制你的能力,而是有很大的权力很大的责任,你自己必须适度。

      您必须自己重构代码以使其正常。我们无法为您做到这一点,因为我们不知道您想要实现什么。谁知道呢,也许你的想法不愧是天才。太糟糕了,我们不知道你的问题。你有一些插件系统。我问你:你需要吗?好的旧 mixin 系统对你来说还不够好吗?你确定你不是用 Ruby 编程 Java 吗?那么,你的应用程序会比 Rails 更强大吗?如果是,请继续使用插件系统。如果不是,那就别再玩弄了,把时间花在编写真正执行您的应用程序应该做的事情的算法上,而不是提前创建不可分割的插件系统。如果您在一家公司工作(我对此表示怀疑),请务必让您的老板阅读此内容。

      【讨论】:

      • 不,我不希望这会成为今天公认的答案。但也许在 3 个月后,在 SO 之外,你会看到曙光 :-)
      • 插件系统是应用程序的核心,Ruby 的动态特性使其成为理想的解决方案。如果有更好的加载方法(myaddon.rb),检测 MyAddon 类并动态加载它——我很想听听。否则,你的回答是无用的,而且有点侮辱性。
      • 你指的other question危害较小,因为它只反映在接收者身上。通过在included实例化 接收器,您为接收器编写器创建了许多不必要的约束。完全具体的问题是:您必须在include 调用中实例化接收者吗?更普遍的问题是,您的应用程序是做什么的?我不相信你必须经历这个才能让它发挥作用。
      • 不,它不需要在包含中实例化接收器。我有一个插件管理器,它扫描插件目录中的 *.rb 文件,在每个文件上调用 load(),然后以某种方式需要实例化所有插件类。如果有更好的方法,我完全赞成。
      • 这样说,听起来比较正常。我只需编写一个脚本,获取您目录中的 *.rb 文件列表(例如,'my_addon.rb'、'his_addon.rb'、'her_addon.rb'),提取文件名,将它们全部加载,然后然后为每个文件名调用new 方法,如MyAddon.new; HisAddon.new, HerAddon.new。我会要求插件编写者他们的文件名在蛇大小写中与他们在骆驼大小写中的类名相同(合理和直观的 C​​oC 要求),并且我会让插件类有责任将自己集成到对象社会中您的应用程序。
      猜你喜欢
      • 2011-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-30
      相关资源
      最近更新 更多