在Ruby模块中,是否有可能覆盖#initialize方法?

11

我一直在尝试想出如何扩展模块中initialize的行为。我希望在被混入的类的initialize中不调用super的情况下完成。我想支持通常的include调用模式,但我无法弄清楚。我已经阅读了所有可以找到的关于此问题的资料,虽然有人给出了建议,但它们似乎都无法正常工作(至少在我的手上)。

以下是我所了解的(我想):

  • 如果可能的话,必须使用include钩子(即Module.included(base))来实现。
  • include钩子将在包含类定义initialize之前执行,因此简单地尝试使用base.instance_eval定义initialize没有意义,因为它将被覆盖。
  • 有人建议利用method_added钩子并在其中处理。我正在尝试这样做,但似乎钩子在方法定义的开始处执行,因此你最终得到的是下面看到的内容。

module Mo
  def self.included(klass)
    klass.instance_eval do
      def method_added(method)
        puts "Starting creation of #{method} for #{self.name}"
        case method
        when :initialize
          alias_method :original_initialize, :initialize
          puts "About to define initialize in Mo"
          def initialize
            original_initialize
            puts "Hello from Mo#initialize"
          end
          puts "Finished defining initialize in Mo"
        end
        puts "Finishing creation of #{method} for #{self.name}"
      end
    end
  end
end

class Foo
  include Mo
  def initialize
    puts "Hello from Foo#initialize"
  end
end

foo = Foo.new

这会产生以下输出:

    Starting creation of initialize for Foo
    Starting creation of original_initialize for Foo
    Finishing creation of original_initialize for Foo
    About to define initialize in Mo
    Finished defining initialize in Mo
    Finishing creation of initialize for Foo
    Hello from Foo#initialize

在我看来,来自Foo类的initialize仍然覆盖了模块的定义。我猜测这是因为该定义仍然处于打开状态,这表明它不是由最后启动的块获胜,而是由最后结束的块获胜。

如果有人真正知道如何做到这一点并使其正常工作,请启迪我。

顺便说一句,是的,我认为有一个充分的理由想要这样做。

4个回答

32

如果您使用的是 Ruby 2.0 或更高版本,则可以直接使用 prepend。要求用户只使用 prepend 而不是 include,或者执行以下操作:

module Mo
  module Initializer
    def initialize
      puts "Hello from Mo#initialize"
      super
    end
  end

  def self.included(klass)
    klass.send :prepend, Initializer
  end
end

我在提问后不久发现了 prepend。我很喜欢它!但我仍然好奇是否有一种方法可以在1.9.*中实现。 - Huliax
2
我使用klass.send:prepend而不是简单地使用klass.prepend,是否有什么明显的遗漏? - NobodysNightmare

7

好的,在Ruby 1.9中,您可以向new类方法添加功能...

module Mo
  def new(*var)
    additional_initialize(*var)
    super(*var)
  end
  def additional_initialize(*var)
    puts "Hello from Mo"
  end
 end

class Foo
  extend Mo
  def initialize
    puts "Hello from Foo"
  end
end

foo = Foo.new

这个函数返回...

Hello from Mo
Hello from Foo

0

如果在Foo的初始化中只有在包含的方法存在时才进行条件调用,这样是否满足要求呢?

module Mo
  def initialize_extra
    puts "Hello from Mo"
  end
end

class Foo
  include Mo
  def initialize
    if defined? initialize_extra
      initialize_extra
    else
      puts "Hello from Foo"
    end
  end
end

x = Foo.new

2
我的目标是除了需要包含模块之外,不给类增加任何负担。 - Huliax

0
如果使用Rails,您也可以像这样使用ActiveSupport :: Concern来实现:
module M
  extend ActiveSupport::Concern

  included do
    attr_reader :some_reader
  end

  def initialize
    puts 'module initialize'
  end
end

class Foo
  include M

  def initialize
    super
    puts 'class initialize'
  end
end

调用 Foo.new 将输出

module initialize
class initialize

如果您感兴趣,也可以阅读this文章


请阅读我原帖的第二句话。整个意图是不要做你所建议的事情。 - Huliax
1
@Huliax 啊,抱歉,我的错。我正在尝试解决我的另一个问题,看到你的帖子时就混淆了。 - Ngoral

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接