在运行时将 class 添加到 ruby 模块

Add class to ruby module in runtime

我需要在运行时将 class 添加到 class(或模块)中。例如:我有 模块 M,Class A. 我需要添加一个新的 Class BModule M。 我试过:

M.module_eval do
  class B
  end
end

但是没用。

我知道(例如)如果我想添加一个方法到现有的class A,我愿意

class << A
def method
end
end

但是如何将 Class 添加到模块或 Class?

这个问题特别关注在运行时添加 class。 class 将在现有模块中创建,但是,正如将会看到的那样,这几乎是偶然的并且实用性值得怀疑。

在运行时向模块添加 class

为了在运行时协助在给定模块内构建 classes,我们可能会构建一个方法 class_factory

def class_factory(mod, class_name, consts, meths, instance_meths,
   accessors)
  class_obj = mod.const_set(class_name, Class.new)
  consts.each { |const,val| class_obj.const_set(const,val) }
  meths.each do |name,body| 
    class_obj.singleton_class.
    instance_eval("define_method(:#{name}) #{body}")
  end
  instance_meths.each do |name,body| 
    class_obj.instance_eval("define_method(:#{name}) #{body}")
  end
  accessors.each do |accessor,inst_var| 
    class_obj.public_send(accessor, inst_var)
  end
  class_obj
end

我们来试试吧。

module M
end

class_obj = class_factory(
  M,
  "B",
  { 'A'=>7, 'D'=>'cat' },
  { greeting: '{ |n| "Cat\'s have #{n} lives" }' },
  { initialize: '{ |name| @name = name }',
    say_name: '{ "My name is #{@name}" }' },
  { attr_accessor: "name" }
)
  #=> M::B 

class_obj == M::B
  #=> true 
M::B.constants
  #=> [:A, :D] 
class_obj.methods(false)
  #=> [:greeting] 
M::B.instance_methods(false)
  #=> [:say_name, :name=, :name] 
class_obj.greeting(9)
  #=> "Cat's have 9 lives" 
M::B.greeting(5) 
  #=> "Cat's have 5 lives" 

instance = M::B.new "Lola" # or class_obj.new "Lola"
  #=> #<M::B:0x000056cb6e766840 @name="Lola"> 
instance.say_name
  #=> "My name is Lola" 
instance.name    
  #=> "Lola" 
instance.name = "Lo"    
  #=> "Lo" 
instance.name    
  #=> "Lo" 

您的代码可能包含诸如此类的静态表达式,唯一的动态部分是 class.

的构造

另一方面,class 也可以动态使用。例如:

mod = "M"
cl  = "B"
name = "Lola"
meth = "say_name"

然后:

Object.const_get("#{mod}::#{cl}").new(name).public_send(meth)
  #=> "My name is Lola" 

class_obj.new(name).public_send(meth)
  #=> "My name is Lola" 

如何最好地引用动态创建的 classes

我们刚刚看到了引用动态创建的 class 的各种方法。根据要求,M::Bclass_objObject.const_get("#{mod}::#{cl}")class_obj。显然,class_obj 的使用在这两种情况下都是最简单的,并且具有额外的优势,即如果将来 MB 中的 class_obj 引用不需要更改 M::B 已更改。

动态创建作为模块成员的 class 是否有优势?

回想一下,在模块中创建 class 的主要原因是创建一个命名空间,因此,例如,M1::CM2::C 不会产生名称冲突。但是,如果我们通过变量(此处为 class_obj)持有的(唯一)对象(而不是其名称,常量)引用动态创建的 class,则不需要命名空间.所以我在这部分提出的问题的答案是 "no".

此外,如果我们通过其对象引用动态创建的 class,则没有理由为 class 分配名称。因此我们可以修改 class_factory 如下:

def class_factory(consts, meths, instance_meths, accessors)
  Class.new do
    consts.each { |const,val| const_set(const,val) }
    meths.each do |name,body| 
          singleton_class.
          instance_eval("define_method(:#{name}) #{body}")
    end
    instance_meths.each do |name,body| 
      instance_eval("define_method(:#{name}) #{body}")
    end
    accessors.each do |accessor,inst_var| 
      public_send(accessor, inst_var)
    end
  end
end

class_obj = class_factory(
  { 'A'=>7, 'D'=>'cat' },
  { greeting: '{ |n| "Cat\'s have #{n} lives" }' },
  { initialize: '{ |name| @name = name }',
     say_name: '{ "My name is #{@name}" }' },
  { attr_accessor: "name" }
)
  #=> #<Class:0x000056cb6eaeefd0>

class_obj持有的对象被称为匿名class,因为它没有名字(这是一个常量)。

class_obj.constants
  #=> [:A, :D] 
class_obj.methods(false)
  #=> [:greeting] 
class_obj.instance_methods(false)
  #=> [:say_name, :name=, :name] 

instance = class_obj.new "Billy-Bob"
  #=> #<#<Class:0x000056cb6eaeefd0>:
  #             0x000056cb6eb183d0 @name="Billy-Bob"> 
instance.say_name
  #=> "My name is Billy-Bob" 
instance.name
  #=> "Billy-Bob" 
instance.name = "BB"
  #=> "BB"