当前位置: 首页 > 知识库问答 >
问题:

从Ruby中的模块/混合中继承类方法

汪皓
2023-03-14

众所周知,在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 1Common 2,我们真的需要混合程序怎么办?)类方法继承不能从混合程序中工作有什么深层原因吗?

共有3个答案

惠野
2023-03-14

正如Sergio在评论中提到的那样,对于已经加入Rails(或者不介意依赖于积极支持)的人来说,关注在这里很有帮助:

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

齐俊贤
2023-03-14

下面是完整的故事,解释了理解模块包含为什么会像Ruby中那样工作所需的必要元编程概念。

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

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中找到该方法:

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中,类和模块是普通对象——它们是类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"

这是自己。singleton_类。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!

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

  1. 现在,我们必须在主机类定义中调用include和extend来正确地包含我们的模块。如果您必须包含许多类似的模块,那么这可能会非常麻烦

那么,这样做如何:当我们在第一行调用include时,我们会以某种方式通知模块它已经被包含,并给它我们的类对象,以便它可以调用扩展本身。这样,如果模块愿意,它的工作就是添加类方法。

这正是特殊的自我。包含的方法用于。每当模块被包含到另一个类(或模块)中时,Ruby就会自动调用此方法,并将宿主类对象作为第一个参数传入:

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中唯一能做的,我们有class对象,所以我们可以在它上面调用任何其他(类)方法:

def self.included(base)  # `base` is `HostClass` in our case
  base.existing_class_method
  #=> "cool"
end
钱志义
2023-03-14

一个常见的习惯用法是使用包含挂钩并从那里注入类方法。

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"
 类似资料:
  • 本文向大家介绍Python tkinter模块中类继承的三种方式分析,包括了Python tkinter模块中类继承的三种方式分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python tkinter模块中类继承的三种方式。分享给大家供大家参考,具体如下: tkinter class继承有三种方式。 提醒注意这几种继承的运行方式 一、继承 object 1.铺tk.Frame给par

  • 类继承 在面向类的语言中,你不仅可以定义一个能够初始化它自己的类,你还可以定义另外一个类 继承 自第一个类。 这第二个类通常被称为“子类”,而第一个类被称为“父类”。这些名词显然来自于亲子关系的比拟,虽然这种比拟有些扭曲,就像你马上要看到的。 当一个家长拥有一个和他有血缘关系的孩子时,家长的遗传性质会被拷贝到孩子身上。明显地,在大多数生物繁殖系统中,双亲都平等地贡献基因进行混合。但是为了这个比拟的

  • 本文向大家介绍JavaScript类的继承方法小结【组合继承分析】,包括了JavaScript类的继承方法小结【组合继承分析】的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了JavaScript类的继承方法。分享给大家供大家参考,具体如下: 在JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量

  • 本文向大家介绍JavaScript中的继承之类继承,包括了JavaScript中的继承之类继承的使用技巧和注意事项,需要的朋友参考一下 继承简介       在JS中继承是一个非常复杂的话题,比其他任何面向对象语言中的继承都复杂得多。在大多数其他面向对象语言中,继承一个类只需使用一个关键字即可。在JS中想要达到继承公用成员的目的,需要采取一系列措施。JS属于原型式继承,得益于这种灵活性,我们既可以

  • 本文向大家介绍Ruby类继承、抽象类、类拓展混入、代理类实例,包括了Ruby类继承、抽象类、类拓展混入、代理类实例的使用技巧和注意事项,需要的朋友参考一下 总结一下工作中遇到的类扩展: 1、类继承: 当多个类公用很多方法的时候可以将公用方法部分抽取出来,需要的类做相关继承。 例子: 2、抽象类 当多个类要继承一个类时,用第一种方法,会遇到一个问题。 (引用一个别人的注解来描述抽象类的运用吧http

  • 问题内容: 在PHP / Java中,可以做到: 并且,Super类的所有公共/受保护的方法,属性,字段等都会自动成为Sub类的一部分,如有必要,可以重写这些类。 Javascript中的等效功能是什么? 问题答案: 我已经更改了现在的操作方式,我尝试避免使用构造函数和它们的属性,但是我从2010年起的旧答案仍然是最底层的。我现在更喜欢。适用于所有现代浏览器。 我应该注意,这通常比使用函数构造函数