Ruby中模块扩展其他模块

4

我在这方面读了很多答案,但我仍然无法理解为什么以下内容不起作用。

module A
  def a
    puts 'hi'
  end
end

module B
  extend A
end

class C
  extend B
  def b
    a
  end
end
C.new.b # undefined local variable or method `a' for #<C:...

我也尝试过以下方法:

module B
  def self.included(recipient)
    recipient.extend A
  end
end

希望 C 能够得到扩展(但我猜等级结构就错了)。
重要提示:问题在于我有一个需要通过 扩展memoist)的模块,并且我想为它添加一些功能。
我该如何实现这一点,为什么第一个示例不起作用?

另请参见http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/。 - Kris
@Kris 是的,我已经阅读了它,但首先这是一个在另一个模块中定义的情况(不确定是否重要),而且更重要的是,它与扩展(以任何方式)必须作为“扩展”导入的方法无关(这是 memoist 的要求)。 - estani
1
我喜欢你创建了一个独立的示例。然而,它可能有些脱离实际。我认为你需要给我们提供一个使用Memoist的示例,这样我们才能看到你真正遇到的问题。 - Wayne Conrad
通常情况下,包含在类或另一个模块中的模块被称为mixin。当您使用extend时,它将创建类方法而不是实例方法。虽然您也可以通过使用一些额外的魔法来使用include创建类方法,这种方法是通过included方法来扩展目标类的嵌套模块,按照惯例称为ClassMethods - Kris
3个回答

4

extends 将传递的模块中的方法添加为“类方法”。你要查找的是include,它将(A模块中的)方法添加为实例方法,就像你想要的那样:

module A
  def a
    puts 'hi'
  end
end

module B
  include A
end

class C
  include B
  def b
    a
  end
end
C.new.b
# hi

抱歉,但类C需要扩展模块A,就像我在问题中提到的那样,这很重要。(这是memoist的要求,我不想解释为什么,但它是必需的)。 - estani
这确实回答了问题。 - Kris
1
@estani请澄清一下。您想将模块A中的方法作为实例方法和类方法吗? 也许您应该描述一下您使用Memoist想要实现什么? - Marek Lipka

2
请注意,如果一个模块M1被另一个模块M2包含扩展(或前置),则M1的实例方法将被引入到M2中,作为实例方法(如果是包含)或模块方法(如果是扩展)。如果M1包含模块方法(在M1上调用的方法),则它们会被includeextend都跳过。由于类也是模块,因此当M2是一个类时,这也适用。
module M1
  def i() end
  def self.m() end
end

M1.instance_methods
  #=> [:i] 
M1.methods(false)
  #=> [:m] 

module M2; end

M2.extend M1
M2.instance_methods
  #=> []
M2.methods & [:i, :m]
  #=> [:i]

module M3; end

M3.include M1
M3.instance_methods
  #=> [:i]
M3.methods & [:i, :m]
  #=> []

我们可以看到,M2M3中都没有包含方法:m。现在考虑以下代码。
module A
  def a
    puts 'hi'
  end
end

module B
  extend A
end

B.instance_methods
  #=> []
B.methods & [:a]
  #=> [:a]

如预期所示,B 中不包含任何实例方法,并且其模块方法包括 :a。从早期的讨论中可以得出结论,将 B 包含或扩展到另一个模块(或类)C 中,不会将 任何实例方法或方法 引入到 C 中。
为了增加 A 的功能,必须将 A include(或 prepend)到 B 中。
module B
  include A
  def b
    puts 'ho'
  end
end

B.instance_methods
  #=> [:b, :a] 
B.methods & [:a, :b]
  #=> []

C根据需求可以包含扩展B

如果在另一个模块中包含扩展该模块时,是否有任何理由定义模块方法?答案是,这些方法只是作为非面向对象编程语言中的函数的辅助方法。例如,模块Math仅包含模块方法。因此,它们在模块上调用;例如,

Math.sqrt(2)
  #=> 1.4142135623730951  

1

首先,你的例子不是完全正确的。一个被扩展的模块默认情况下不能添加实例方法。你的A模块应该如下所示:

module Memoist
  def self.extended(mod)
    mod.include InstanceMethods
  end

  module InstanceMethods
    def a
      puts 'hi'
    end
  end
end

你可以在扩展接口的情况下,通过 extendedincludedexcluded 回调函数来 扩展 Memoist。这些回调函数会在你的模块被扩展、包含或预置之后触发。但是需要注意的是,如果你想要使用 super 来扩展一个已有的方法,这种方式是行不通的,因为原始方法会在你的版本之前被调用。
module YourMemoist
  # Depending on how you want to incorporate your module into the class
  # you can use one of the following: 
  def self.extended(mod)
    mod.extend Memoist
  end

  # def self.included(mod)
  #   mod.extend Memoist
  # end

  # def self.prepended(mod)
  #   mod.extend Memoist
  # end

  # ...
end

class C
  extend MemoistHelper # or include, prepend
end

C.new.a #=> prints hi
C.singleton_class.ancesotrs #=> [#<Class:C>, Memoist, YourMemoist, ...]

如果您确实想使用super关键字,事情会变得有些困难。因为需要先扩展Memoist模块。一种方法是将该模块包含到您自己的模块中,以便您可以覆盖它们。到目前为止还很简单。但是,Memoist可能已经定义了一个extended回调函数(用于添加实例方法),当我们将该模块包含在自己的模块中时,该回调不会被触发。因此,我们需要手动调用它。
module YourMemoist
  include Memoist

  def self.extended(mod)
    Memoist.send(:extended, mod) # using #send to call private method
    # If you want to add your own instance methods add them after the
    # above call (as shown in the Memoist module).
  end

  # ...
end

class C
  extend MemoistHelper
end

C.new.a #=> prints hi
C.singleton_class.ancesotrs #=> [#<Class:C>, YourMemoist, Memoist, ...]

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