首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >从Ruby中的模块/ mixins继承类方法

从Ruby中的模块/ mixins继承类方法
EN

Stack Overflow用户
提问于 2012-05-22 05:34:11
回答 4查看 47.8K关注 0票数 104

众所周知,在Ruby中,类方法是继承的:

代码语言:javascript
运行
复制
class P
  def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works

然而,令我惊讶的是,它不能与mixins一起工作:

代码语言:javascript
运行
复制
module M
  def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!

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

代码语言:javascript
运行
复制
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works

但我正在编写一个包含实例方法和类方法的混入(或者,更确切地说,是想写):

代码语言:javascript
运行
复制
module Common
  def self.class_method; puts "class method here" end
  def instance_method; puts "instance method here" end
end

现在我想做的是:

代码语言:javascript
运行
复制
class A; include Common
  # custom part for A
end
class B; include Common
  # custom part for B
end

我希望A、B从Common模块继承实例方法和类方法。但当然,这是行不通的。那么,有没有一种秘密的方法可以让这种继承从一个模块开始工作呢?

在我看来,将它分成两个不同的模块是不优雅的,一个模块用于包含,另一个模块用于扩展。另一种可能的解决方案是使用类Common而不是模块。但这只是一种变通方法。(如果有两组通用功能Common1Common2,而我们真的需要使用mixins,该怎么办?)有没有什么深层次的原因导致类方法继承不能通过mixins工作?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-05-22 05:38:26

一种常见的习惯用法是使用included钩子并从那里注入类方法。

代码语言:javascript
运行
复制
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"
票数 177
EN

Stack Overflow用户

发布于 2017-07-16 18:10:38

这里是完整的故事,解释了理解模块包含在Ruby中的工作方式所需的必要元编程概念。

当包含一个模块时会发生什么?

将模块包含到类中会将模块添加到类的祖先中。您可以通过调用任何类或模块的ancestors方法来查看其祖先:

代码语言:javascript
运行
复制
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的一个实例上调用一个方法时,Ruby将查看这个祖先列表中的每一项,以便找到一个具有所提供名称的实例方法。因为我们将M包含在C中,所以M现在是C的祖先,所以当我们在C的实例上调用foo时,Ruby会在M中找到该方法

代码语言:javascript
运行
复制
C.new.foo
#=> "foo"

请注意,included不会将任何实例或类方法复制到类中-它只是向类添加了一个“注意”,即它还应该在包含的模块中查找实例方法。

那么我们模块中的"class“方法呢?

由于包含只会更改实例方法的分派方式,因此将模块包含到类中只会使其实例方法在该类上可用。模块中的"class“方法和其他声明不会自动复制到类中:

代码语言:javascript
运行
复制
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的实例。这意味着您可以动态创建新类,将它们分配给变量,等等:

代码语言:javascript
运行
复制
klass = Class.new do
  def foo
    "foo"
  end
end
#=> #<Class:0x2b613d0>

klass.new.foo
#=> "foo"

同样在Ruby语言中,您可以在对象上定义所谓的单例方法。这些方法作为新的实例方法添加到对象的特殊的、隐藏的单例类中:

代码语言:javascript
运行
复制
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]

但是类和模块不也只是普通的对象吗?事实上,他们就是!这是否意味着他们也可以使用单例方法?是的,确实如此!这就是类方法是如何产生的:

代码语言:javascript
运行
复制
class Abc
end

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

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

或者,更常见的定义类方法的方法是在类定义块中使用self,它引用正在创建的类对象:

代码语言:javascript
运行
复制
class Abc
  def self.foo
    "foo"
  end
end

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

如何在模块中包含类方法?

正如我们刚刚建立的,类方法实际上只是类对象的单例类上的实例方法。这是否意味着我们可以在单例类中包含一个模块来添加一堆类方法?是的,确实如此!

代码语言:javascript
运行
复制
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添加了,它做了同样的事情--即在对象的单例类中包含了一个模块:

代码语言:javascript
运行
复制
class HostKlass
  include M
  extend M::ClassMethods
end

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

extend调用移动到模块中

前面的示例不是结构良好的代码,原因有两个:

  1. 我们现在必须在HostClass定义中同时调用includeextend才能正确包含我们的模块。如果您必须包含大量类似的modules.
  2. HostClass直接引用M::ClassMethods,这会变得非常麻烦,这是HostClass不需要知道或关心的模块M的实现细节。

这样如何:当我们在第一行调用include时,我们以某种方式通知模块它已被包括在内,并且还为它提供了我们的类对象,以便它可以调用extend本身。这样,模块的工作就是根据需要添加类方法。

这正是特殊的self.included 方法的用途。每当模块包含在另一个类(或模块)中时,Ruby都会自动调用此方法,并将host类对象作为第一个参数传入:

代码语言:javascript
运行
复制
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中唯一能做的事情。我们有类对象,所以我们可以在它上面调用任何其他(类)方法:

代码语言:javascript
运行
复制
def self.included(base)  # `base` is `HostClass` in our case
  base.existing_class_method
  #=> "cool"
end
票数 59
EN

Stack Overflow用户

发布于 2017-06-23 11:36:13

正如Sergio在评论中提到的,对于已经在Rails中的人(或者不介意依赖于Active Support),Concern在这里是有帮助的:

代码语言:javascript
运行
复制
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
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10692961

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档