Этот вопрос конкретно касается добавления класса во время выполнения. Класс должен быть создан в существующем модуле, но, как будет видно, это почти случайно и имеет сомнительную полезность.
Добавление класса в модуль во время выполнения
Чтобы помочь в построении классов в данном модуле во время выполнения, мы могли бы создать метод 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"