Добавить класс к ruby модулю во время выполнения - PullRequest
1 голос
/ 27 февраля 2020

Мне нужно добавить класс в класс (или модуль) во время выполнения. Например: у меня есть Модуль M, класс A. Мне нужно добавить новый Класс B в Модуль M . Я пытался сделать:

M.module_eval do
  class B
  end
end

Но это не работает.

Я знаю, что (например), если я хочу добавить метод к существующим класс A , я бы сделал

class << A
def method
end
end

Но как добавить класс в модуль или класс?

1 Ответ

3 голосов
/ 27 февраля 2020

Этот вопрос конкретно касается добавления класса во время выполнения. Класс должен быть создан в существующем модуле, но, как будет видно, это почти случайно и имеет сомнительную полезность.

Добавление класса в модуль во время выполнения

Чтобы помочь в построении классов в данном модуле во время выполнения, мы могли бы создать метод 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" 

Возможно, ваш код может содержат выражения stati c, подобные этим, и единственная динамическая часть c является конструкцией класса.

С другой стороны, класс также может использоваться динамически. Например:

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" 

Как лучше всего ссылаться на динамически созданные классы

Мы только что видели различные способы ссылки на динамически создаваемый класс. В зависимости от требований, M::B против class_obj и Object.const_get("#{mod}::#{cl}") против class_obj. Очевидно, что использование class_obj является самым простым в обоих случаях и имеет дополнительное преимущество, заключающееся в том, что ссылки на class_obj не нужно менять, если в будущем M или B в M::B будут изменены.

Преимущества динамического создания классов, которые являются членами модуля?

Напомним, что основной причиной создания класса в модуле является создание пространства имен, так что для Например, M1::C и M2::C не создают конфликт имен. Однако если мы ссылаемся на динамически созданный класс по его (уникальному) объекту (а не по имени, по константе), который содержится в переменной (здесь class_obj), пространство имен не требуется. Таким образом, ответ на вопрос, который я поставил в этом разделе, - «нет».

Более того, если мы ссылаемся на динамически созданный класс по его объекту, нет причин назначать имя классу. Поэтому мы можем изменить 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_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" 
...