从Ruby中的模块/混入继承类的方法(Inheriting class methods from m

2019-06-24 15:56发布

据了解,在Ruby中,类方法得到继承:

class P
  def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works

然而,这是一个意外,我认为它不与混入工作:

module M
  def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!

我知道#extend方法可以做到这一点:

module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works

但我写一个mixin(或者说,想编写)含有实例方法和类方法:

module Common
  def self.class_method; puts "class method here" end
  def instance_method; puts "instance method here" end
end

现在,我想这样做,这是什么:

class A; include Common
  # custom part for A
end
class B; include Common
  # custom part for B
end

我想A,B继承实例和类方法Common模块。 不过,很显然,这是行不通的。 那么,是不是有由单一模块使这种继承工作的秘密呢?

这似乎不雅给我这个分裂成两个不同的模块,一个包括其他的扩展。 另一种可能的解决方案是使用一类Common的,而不是一个模块。 但是,这只是一种变通方法。 (如果有两套常用功能的Common1Common2 ,我们真的需要有混入?)有什么深层原因类方法的继承不会混入工作?

Answer 1:

一个常见的成语是使用included钩,并从那里注入类方法。

module Foo
  def self.included base
    base.send :include, InstanceMethods
    base.extend ClassMethods
  end

  module InstanceMethods
    def bar1
      'bar1'
    end
  end

  module ClassMethods
    def bar2
      'bar2'
    end
  end
end

class Test
  include Foo
end

Test.new.bar1 # => "bar1"
Test.bar2 # => "bar2"


Answer 2:

下面是完整的故事,解释明白,为什么模块工作纳入它在Ruby的方式所需要的必要的元编程的概念。

当包括模块会发生什么?

包括模块插入类添加模块类的祖先 。 你可以看一下任何类或模块的祖先通过调用其ancestors的方法:

module M
  def foo; "foo"; end
end

class C
  include M

  def bar; "bar"; end
end

C.ancestors
#=> [C, M, Object, Kernel, BasicObject]
#       ^ look, it's right here!

当你调用一个方法上的一个实例C ,红宝石会看看这个祖先列表的每一个项目,以便找到与所提供的名称的实例方法 。 因为我们包括MCM是现在的祖先C ,所以当我们调用foo上的一个实例C ,红宝石会发现方法M

C.new.foo
#=> "foo"

需要注意的是包含不会任何实例或类方法复制到类 -它只是增加了一个“注”的类,它也应该寻找包括模块中的实例方法。

那我们的模块中的“类”的方法呢?

因为列入只是改变了方法实例方法分派,其中包括一个模块插入一个类只能使这个类的可用的实例方法 。 模块中的“级”的方法和其他声明不会自动复制到类中:

module M
  def instance_method
    "foo"
  end

  def self.class_method
    "bar"
  end
end

class C
  include M
end

M.class_method
#=> "bar"

C.new.instance_method
#=> "foo"

C.class_method
#=> NoMethodError: undefined method `class_method' for C:Class

如何实现的Ruby类的方法?

在Ruby中,类和模块是普通的对象-它们是类的实例ClassModule 。 这意味着你可以动态创建新类,将它们分配给变量等:

klass = Class.new do
  def foo
    "foo"
  end
end
#=> #<Class:0x2b613d0>

klass.new.foo
#=> "foo"

此外,在Ruby中,你必须定义在对象上所谓的单态方法的可能性。 这些方法会添加新的实例方法的特殊隐藏单例类的对象:

obj = Object.new

# define singleton method
def obj.foo
  "foo"
end

# here is our singleton method, on the singleton class of `obj`:
obj.singleton_class.instance_methods(false)
#=> [:foo]

但不是类和模块只是普通的对象呢? 事实上,他们是! 这是否意味着他们可以有单方法呢? 是的,它确实! 这是类的方法是如何诞生的:

class Abc
end

# define singleton method
def Abc.foo
  "foo"
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]

或者,定义一个类方法的更常见的方法是使用self类定义块,这是指被创建的类对象中:

class Abc
  def self.foo
    "foo"
  end
end

Abc.singleton_class.instance_methods(false)
#=> [:foo]

如何包括模块中的类的方法?

当我们刚成立不久,类方法是真正的单身类的类对象的只是实例方法。 这是否意味着我们可以只包括一个模块插入单类添加了一堆类的方法? 是的,它确实!

module M
  def new_instance_method; "hi"; end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M
  self.singleton_class.include M::ClassMethods
end

HostKlass.new_class_method
#=> "hello"

self.singleton_class.include M::ClassMethods行不会看起来非常漂亮,因此Ruby添加Object#extend ,这不相同-即包括模块到单独的类的对象:

class HostKlass
  include M
  extend M::ClassMethods
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ there it is!

移动extend呼叫到模块

这前面的例子不是结构良好的代码,原因有二:

  1. 我们现在必须同时调用includeextendHostClass定义中,以正确包括我们的模块。 如果你必须包括许多类似的模块这会造成非常繁琐。
  2. HostClass直接引用M::ClassMethods ,这是模块的实现细节 MHostClass应该不需要知道或关心。

那么这个怎么样:当我们调用include在第一行,我们以某种方式通知,它已被列入模块,并且还给予它我们的类对象,以便它可以调用extend本身。 这样一来,它的模块的工作,如果要添加的类方法。

这也正是特殊的东西self.included方法是。 红宝石自动调用此方法每当模块被包括到另一个类(或模块),并通过在宿主类对象作为第一个参数:

module M
  def new_instance_method; "hi"; end

  def self.included(base)  # `base` is `HostClass` in our case
    base.extend ClassMethods
  end

  module ClassMethods
    def new_class_method; "hello"; end
  end
end

class HostKlass
  include M

  def self.existing_class_method; "cool"; end
end

HostKlass.singleton_class.included_modules
#=> [M::ClassMethods, Kernel]
#    ^ still there!

当然,添加类的方法是不是我们可以做的唯一的事情self.included 。 我们有一流的对象,所以我们可以调用它的任何其它(类)方法:

def self.included(base)  # `base` is `HostClass` in our case
  base.existing_class_method
  #=> "cool"
end


Answer 3:

由于塞尔吉奥的意见,对于球员谁已经在铁轨(或者根据不介意提到主动支持 ), Concern是有帮助的位置:

require 'active_support/concern'

module Common
  extend ActiveSupport::Concern

  def instance_method
    puts "instance method here"
  end

  class_methods do
    def class_method
      puts "class method here"
    end
  end
end

class A
  include Common
end


Answer 4:

你可以有你的蛋糕,通过这样吃太:

module M
  def self.included(base)
    base.class_eval do # do anything you would do at class level
      def self.doit #class method
        @@fred = "Flintstone"
        "class method doit called"
      end # class method define
      def doit(str) #instance method
        @@common_var = "all instances"
        @instance_var = str
        "instance method doit called"
      end
      def get_them
        [@@common_var,@instance_var,@@fred]
      end
    end # class_eval
  end # included
end # module

class F; end
F.include M

F.doit  # >> "class method doit called"
a = F.new
b = F.new
a.doit("Yo") # "instance method doit called"
b.doit("Ho") # "instance method doit called"
a.get_them # >> ["all instances", "Yo", "Flintstone"]
b.get_them # >> ["all instances", "Ho", "Flintstone"]

如果要添加实例,类变量,你最终会拉你的头发,除非你做这种方式你会遇到一堆断码。



文章来源: Inheriting class methods from modules / mixins in Ruby
标签: ruby mixins