既可以作为 class 方法调用又可以作为 class 方法混合的方法?

method that is both callable as class method and mixed in as a class method?

在Ruby中,是否可以定义一个方法,可以直接调用为class方法,也可以混入为class方法?也就是说,不使用 self.includedself.extended 创建等效的 class 或实例方法。

这些方法都不起作用:

module A
  def foo(s)
    puts s
  end
end

class One
  extend A
end

One.foo("one")
#A.foo("a")

module B
  def self.foo(s)
    puts s
  end
end

class Two
  include B
end

#Two.foo("two")
B.foo("b")

似乎对所问的内容有些困惑。这是一个不太抽象的场景。 A是可以直接使用的mixin。 B 是一个 mixin,旨在独立于 A 使用,即 "wraps" A 的方法之一。

module A
  # #foo has to be defined in order to be mixed in via `extend`.
  # Being mixed in via `include` has the same issue but inverted.
  def foo(s) A.foo(s) end 
  def self.foo(s) puts "A: " + s end
end

module B
  def foo(s) A.foo("B: " + s) end
end

class One; extend A end

class Two; extend B end

One.foo("one")
Two.foo("two")

为了使其工作,A#fooA::foo 都必须单独定义。 Module#module_function 等现有设施在这种情况下不起作用。

你可以这样写:

module A
  def foo(s)
    puts s
  end
end

class One
  singleton_class.include A
end

One.foo('hi')
  #=> 'hi'

但这非常接近 Object#extend

如果:

module A
  def self.foo(s)
    puts s
  end
end

并且您想知道是否可以从 class C 引用 A 以使 foo 成为 [=13= 的 class 方法],我的理解是答案是"no"。我说 "my understanding" 因为它不是可以证明的东西;据我了解,这是 Matz 做出的设计决定。

我会尽量不明确,但据我所知,你的问题的答案是否定的。如果你想混合实例和 class 方法,那么标准方法是这样的:

module A 
  def self.included(base)
    #this will extend the class you included A in
    #using A::ClassMethods definition
    base.extend(ClassMethods)
  end
  #these methods will be added as class_methods to any class
  #that includes A
  module ClassMethods
    def foo(s)
      "You fooed the class with #{s}"
    end
  end
  #this will be added as an instance method as it would be in a standard include
  def bar(s)
    "You barred an instance with #{s}"
  end
end

class Mixed
  include A
end
Mixed.foo("Hello")
#=> "You fooed the class with Hello"
Mixed.new.bar("Hello")
#=> "You barred an instance with Hello"

我希望这能回答你的问题,因为你的意图有点不清楚。由于您的问题似乎不需要实例方法,您也可以这样做

module A 
  def foo(s)
    "called foo with #{s}"
  end
end
module B
  include A
  alias_method :a_foo, :foo
  def foo(s)
    "B called foo from A #{a_foo(s)}"
  end
end
class Mixed
  extend B
end

Mixed.foo("Mixed")
#=>"B called foo from A called foo with Mixed"

再更新一次

这是一个奇怪的模式,但我相信它适用于您的用例

module A
  def foo(s)
    "fooed with #{s}"
  end
  def bar(s)
    "barred with #{s}"
  end
end

module B
  include A
  included_modules.each do |mod|
    (mod.instance_methods - Object.methods).each do |meth|
      alias_method "#{mod.name.downcase}_#{meth}", meth
    end
  end
end

class Mixed
  extend B
end

Mixed.methods - Object.methods 
#=> [:a_foo, :a_bar, :foo, :bar]

这样你就可以覆盖 B 中的方法并调用 A 版本,但如果你不覆盖它仍然会调用 A 版本。

如果你想让这个功能通用

,你也可以猴子修补 Module class
class Module
  def include_with_namespace(*mods)
    #Module#include runs in reverse so to maintain consistency my patch does as well
    mods.reverse.each do |mod|
      include mod
      (mod.instance_methods - Object.methods).each do |meth|
        alias_method "#{mod.name.downcase}_#{meth}", meth
      end
    end
  end
end 

这样就可以了

module C
  def foo(s)
    "C's foo with #{s}"
  end
  def see_me
    "You can see C"
  end 
end

module B;include_with_namespace A, C; end

class Mixed;extend B;end

Mixed.methods - Object.methods
#=> [:a_foo, :a_bar, :c_foo,:c_see_me, :foo, :bar, :see_me]
Mixed.foo("name")
#=> "fooed with name"
Mixed.c_foo("name")
#=> "C's foo with name"