是否可以在Ruby模块中覆盖#initialize?

时间:2022-06-13 11:00:29

I have been trying to figure out how to extend the behavior of initialize from a module. I want to do it without calling super in initialize of the class that is being mixed into. I want to support the normal pattern of calling include I can't figure it out. I've read everything I can find on the matter and, while people to have suggestions, none of them actually seem to work (in my hands at least).

我一直试图弄清楚如何扩展模块初始化的行为。我想这样做,而不是在正在混合的类的初始化中调用super。我想支持正常的调用模式包括我无法弄明白。我已经阅读了关于这个问题我能找到的所有内容,虽然有人提出建议,但实际上它们似乎都没有(至少在我手中)。

Here is what I (think) I know:

这是我(我想)我知道的:

  • If it can be done at all, it has to be done using the hook on include (i.e. Module.included(base)).
  • 如果它可以完成,则必须使用include on hook(即Module.included(base))来完成。
  • The include hook will execute before the including class defines initialize so there is no point to simply trying to define initialize with base.instance_eval because it will get overwritten.
  • include钩子将在include类定义initialize之前执行,因此没有必要简单地尝试使用base.instance_eval定义initialize,因为它会被覆盖。
  • A suggestion was made to make use of the method_added hook and deal with it in there. That is what I'm trying now but it appears that the hook executes at the beginning of method definition so you end up with what you see below.

    建议使用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
    

This results in the following output:

这导致以下输出:

    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

It looks to me like initialize from class Foo is still overwriting the definition from the module. I'm guessing that this is because the definition is still open, suggesting that it isn't a matter of which block is started last be which is finished last that "wins".

在我看来,从类Foo初始化仍然会覆盖模块中的定义。我猜这是因为定义仍然是开放的,这表明不是最后一个块启动的问题,最后是“胜利”。

If anyone out there really knows how to do this and have it work please enlighten me.

如果有人真的知道如何做到这一点并让它工作,请启发我。

FWIW, yes, I think I have a good reason for wanting to do this.

FWIW,是的,我认为我有充分的理由想要这样做。

3 个解决方案

#1


26  

If you're on Ruby 2.0 or later, you can just use prepend. Either require the user to prepend rather than include, or do:

如果您使用的是Ruby 2.0或更高版本,则可以使用prepend。要么用户要先添加而不是包含,要么执行:

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

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

#2


6  

Ok, well in Ruby 1.9 you could add functionality to the new class method...

好的,在Ruby 1.9中你可以为新的类方法添加功能......

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

That returns...

那回来......

Hello from Mo
Hello from Foo

#3


0  

Would it satisfy to have a conditional call in Foo's initialize that only calls an included method if it's present?

在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

#1


26  

If you're on Ruby 2.0 or later, you can just use prepend. Either require the user to prepend rather than include, or do:

如果您使用的是Ruby 2.0或更高版本,则可以使用prepend。要么用户要先添加而不是包含,要么执行:

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

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

#2


6  

Ok, well in Ruby 1.9 you could add functionality to the new class method...

好的,在Ruby 1.9中你可以为新的类方法添加功能......

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

That returns...

那回来......

Hello from Mo
Hello from Foo

#3


0  

Would it satisfy to have a conditional call in Foo's initialize that only calls an included method if it's present?

在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