Вот ваш код, слегка измененный.
class Parent
def self.create_singleton_variable
@arr = [1,2,3]
end
def self.arr
puts "self = #{self} in the getter for @arr"
@arr
end
end
class Child < Parent
def get
puts self.arr
end
def self.get
puts self.arr
end
end
Я написал Parent
более обычным способом. За исключением добавления оператора puts
, оно эквивалентно тому, которое содержится в вопросе.
Во-первых, главный удар: Ядро # ставит - все возвращается nil
. Вам необходимо удалить puts
из обоих методов:
class Child < Parent
def get
self.arr
end
def self.get
self.arr
end
end
Parent.create_singleton_variable
#=> [1, 2, 3]
Child.get.nil?
self = Child in the getter for @arr
#=> true
Мы видим, что в получателе arr
, вызванном Child
классовым методом get
, self
равно Child
, поэтому метод ищет переменную экземпляра класса @arr
из Child
не из Parent
. Поскольку никакая переменная экземпляра не была инициализирована, возвращается nil
.
Вам нужно следующее.
class Parent
class << self
def create_singleton_variable
@arr = [1,2,3]
end
def arr
puts "self = #{self} in the getter for @arr"
@arr
end
end
end
class Child < Parent
def get
self.class.superclass.arr
end
def self.get
superclass.arr
end
end
Принципиальное отличие от приведенного в вопросе заключается в том, что Класс # суперкласс меняет область действия (т. Е. self
) на Parent
.
Видим, желаемый результат получен.
Child.get
self = Parent in the getter for @arr
#=> [1, 2, 3]
Child.new.class.superclass.arr
self = Parent in the getter for @arr
#=> [1, 2, 3]
Распространенным заблуждением является то, что метод класса Child
, определенный def self.get; self.arr; end
, вызывает получатель Parent::arr
и, следовательно, возвращает значение переменной экземпляра Parent
@arr
. Это Child::arr
, который вызывается, однако, этот метод был унаследован от Parent
, и это переменная экземпляра класса Child
@arr
, которая извлекается, тонкое, но важное различие.
Редактировать 2
Первое наблюдение состоит в том, что Parent
можно записать более обычным (и полностью эквивалентным) способом.
class Parent
def self.create_singleton_variable
@arr = [1,2,3]
puts "arr is initialized #{@arr}"
end
def self.arr
puts "arr is #{@arr.inspect}"
@arr
end
end
Независимо от того, как написано, self
будет равняться Parent
, если какой-либо метод класса задействован для родителя. Поэтому первый создаст переменные экземпляра класса @arr
.
Parent.methods(false)
#=> [:create_singleton_variable, :arr]
Parent.instance_variables
#=> []
Parent.ancestors
#=> [Parent, Object, Kernel, BasicObject]
Теперь давайте создадим переменную класса для Parent
.
Parent.create_singleton_variable
# arr is initialized [1, 2, 3]
Parent.instance_variables
#=> [:@arr]
Теперь позвольте мне изменить значение @arr
.
Parent.instance_variable_set(:@arr, ['dog', 'cat'])
#=> ["dog", "cat"]
Parent.arr
# arr is ["dog", "cat"]
#=> ["dog", "cat"]
Затем создайте класс Child
, но еще не добавляйте модуль в начале.
class Child < Parent
create_singleton_variable
arr
end
arr is initialized [1, 2, 3]
arr is [1, 2, 3]
Child.ancestors
#=> [Child, Parent, Object, Kernel, BasicObject]
Child.instance_variables
#=> [:@arr]
Child.instance_variable_get(:@arr)
#=> [1, 2, 3]
Сюрпризов нет. Затем загрузите модуль.
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "self=#{self}"
puts "superclass=#{superclass}"
puts "self.class=#{self.class}"
puts "self.class.superclass == #{self.class.superclass}"
puts "superclass.arr == #{superclass.arr.inspect}"
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
rescue Exception => e
# do nothing, this is just so you can see arr is actually
# initialized in the context of the Child
puts "Exception => e=#{e}"
end
end
end
(Примечание self.
не требуется в "superclass.arr == #{superclass.arr.inspect}"
) Теперь добавьте этот модуль к Parent
.
Parent.prepend CompletionGeneration
Parent.ancestors
#=> [CompletionGeneration, Parent, Object, Kernel, BasicObject]
Parent.methods.include?(:completion)
#=> true
Child.ancestors
#=> [Child, CompletionGeneration, Parent, Object, Kernel, BasicObject]
Child.methods.include?(:completion)
#=> true
Метод модулей обратного вызова CompletionGeneration::prepended
запускается с base
, равным Parent
, в результате чего синглтон-класс Parent
предшествует ClassMethods
, тем самым добавляя метод класса Parent::completion
. Поскольку у Parent
ранее не было метода с таким именем, использование prepend
или include
имело бы такой же эффект. Кроме того, вместо Parent.singleton_class.include ClassMethods
можно было бы использовать обратный вызов included(base)
и выполнить Parent.extend ClassMethods
. Возможно, prepend
используется здесь для общего случая, когда Parent
может иметь метод класса с таким именем. 1
Теперь выполните следующее.
Child.completion
self=Child
superclass=Parent
self.class=Class
self.class.superclass == Module
arr is ["dog", "cat"]
superclass.arr == ["dog", "cat"]
Exception => e=undefined method `arr' for Module:Class
Исключение возникло, когда
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
казнили. Как это составляет
puts "self.class.superclass.arr == #{Module.arr}"
но, конечно, Module
не имеет модульного метода arr
.
1 С учетом Child.ancestors
, добавление Parent
к модулю приводит к тому, что дочерние элементы Parent
только include
(вместо prepend
) модуля; то есть, если у ребенка уже есть метод completion
до добавления, этот метод не будет заменен методом модуля с тем же именем.