【问题标题】:Initializing instance variables in Mixins在 Mixins 中初始化实例变量
【发布时间】:2012-09-17 03:41:08
【问题描述】:

是否有任何干净的方法来初始化要用作 Mixin 的模块中的实例变量?例如,我有以下内容:

module Example

  def on(...)   
    @handlers ||= {} 
    # do something with @handlers
  end

  def all(...)
    @all_handlers ||= []
    # do something with @all_handlers
  end

  def unhandled(...)
    @unhandled ||= []
    # do something with unhandled
  end

  def do_something(..)
    @handlers     ||= {}
    @unhandled    ||= []
    @all_handlers ||= []

    # potentially do something with any of the 3 above
  end

end

请注意,我必须一次又一次地检查每个 @member 是否已在每个函数中正确初始化——这有点烦人。我宁愿写:

module Example

  def initialize
    @handlers     = {}
    @unhandled    = []
    @all_handlers = []
  end

  # or
  @handlers  = {}
  @unhandled = []
  # ...
end

并且不必反复确保事物已正确初始化。但是,据我所知,这是不可能的。除了将initialize_me 方法添加到Example 并从扩展类调用initialize_me 之外,还有什么办法可以解决这个问题?我确实看到了this example,但我无法将猴子修补到Class 中来完成此操作。

【问题讨论】:

    标签: ruby mixins


    【解决方案1】:

    我认为可能有一个更简单的答案。该模块应该有一个初始化程序,可以像往常一样初始化变量。在包含模块的类的初始化程序中,调用 super() 以调用包含模块中的初始化程序。这只是遵循 Ruby 中的方法调度规则。

    仔细想想,如果包含模块的类也有一个需要初始化的超类,这将不会很好地工作。模块中的初始化程序需要接受可变参数列表并将其传递给超类。不过,这似乎是一个很好的探索途径。

    【讨论】:

      【解决方案2】:

      也许这有点 hacky,但您可以使用 prepend 来获得所需的行为:

      module Foo
        def initialize(*args)
          @instance_var = []
          super
        end
      end
      
      class A
        prepend Foo
      end
      

      这是控制台的输出:

      2.1.1 :011 > A.new
       => #<A:0x00000101131788 @instance_var=[]>
      

      【讨论】:

      • 使用前置而不是包含有什么缺点吗?
      • 我能想到的一个缺点是模块被插入到层次结构中的类下方。通常,我们的类型层次结构看起来像A &lt; Foo &lt; Object,但现在我们有了Foo &lt; A &lt; Object。这意味着 Foo 中的方法会覆盖 A 中的方法,这与我们通常期望的相反。
      • 好吧 :) 好吧,我想“前置”是不言自明的,所以我喜欢(并使用)你的解决方案,谢谢! (+1 次)
      【解决方案3】:
      module Example
        def self.included(base)
          base.instance_variable_set :@example_ivar, :foo
        end
      end
      

      编辑:注意这是设置类实例变量。当模块混合到类中时,无法创建实例上的实例变量,因为这些实例尚未创建。不过,您可以在 mixin 中创建一个初始化方法,例如:

      module Example
        def self.included(base)
          base.class_exec do
            def initialize
              @example_ivar = :foo
            end
          end
        end
      end
      

      在调用包含类的初始化方法时可能有一种方法可以做到这一点(有人吗?)。不确定。但这里有一个替代方案:

      class Foo
        include Example
      
        def initialize
          @foo = :bar
          after_initialize
        end
      end
      
      module Example
        def after_initialize
          @example_ivar = :foo
        end
      end
      

      【讨论】:

      • 太好了,谢谢——我想知道为什么我没有看到任何地方提到过这种方法,尽管有上百万篇关于它的文章。
      • 不幸的是,这似乎不起作用 - 引用 @example_ivar 然后返回为 nil
      • 啊,我明白了,谢谢——但是如果类已经指定了 initialize 方法,这会导致问题,对吧?
      • 谢谢森。 after_initialize 是一个 ActiveRecord 回调,对吧?在这种情况下,我没有使用 ActiveRecord/Rails,只是使用普通的 Ruby。可能是我希望它的工作方式不受语言/这种设计模式的支持。
      • @JohnLedbetter,您可以随意命名after_initialize 方法。这只是在Foo#initialize 上从FooExample 发送消息的一种方式。此处与ActiveRecord 无关。
      【解决方案4】:

      modules 提供挂钩,如Module#included。我建议您查看有关该主题的 ruby​​ 文档,或使用ActiveSupport::Concern,它提供了一些关于模块的帮助。

      【讨论】:

      • 我没有使用ActiveSupport,但感谢您为我指明了正确的方向。
      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 2010-10-18
      • 2011-06-21
      • 2013-08-14
      • 2017-04-05
      • 2011-03-18
      • 2010-11-26
      • 1970-01-01
      相关资源
      最近更新 更多