Ситуация: У меня есть несколько классов, каждый из которых должен содержать переменную с хэшем конфигурации; для каждого класса свой хэш, но одинаковый для всех экземпляров класса.
Сначала я пытался сделать вот так
class A
def self.init config
@@config = config
end
def config
@@config
end
end
class B < A; end
class C < A; end
Но вскоре заметил, что так не получится, потому что @@config хранится в контексте A, а не B или C, таким образом:
B.init "bar"
p B.new.config # => "bar"
p C.new.config # => "bar" - which would be nil if B had it's own @@config
C.init "foo"
p B.new.config # => "foo" - which would still be "bar" if C had it's own @@config
p C.new.config # => "foo"
Я подумал о том, чтобы использовать его следующим образом:
modules = [B, C]
modules.each do |m|
m.init(@config[m.name])
end
# ...
B.new # which should then have the correct config
Теперь мне понятно, почему так происходит, но я не уверен в причине, по которой это происходит именно так.
Не может ли это работать и в обратную сторону, удерживая переменную класса в контексте подкласса?
Меня также раздражает тот факт, что self всегда является подклассом, даже когда вызывается 'в' суперклассе. Исходя из этого, я сначала ожидал, что код из суперкласса "выполняется в контексте" подкласса.
Буду очень признателен за разъяснения по этому поводу.
С другой стороны, скорее всего, мне придется смириться с тем, что это работает именно так, и что я должен найти другой способ сделать это.
Есть ли "мета" способ сделать это? (Я пытался использовать class_variable_set и т.д., но безуспешно).
Или, может быть, вся идея этого метода 'init' изначально ошибочна, и есть какой-то другой "паттерн" для этого?
Я мог бы просто сделать @@config хэшем, хранящим все конфигурации, и всегда выбирать нужную, но мне кажется это немного неудобным... (разве наследование не предназначено для решения подобных проблем? ;)
Переменные @@variables
не являются переменными класса. Это переменные иерархии классов, т.е. они разделяются между всей иерархией классов, включая все подклассы и все экземпляры всех подклассов. (Было предложено думать о @@переменных'' как о
$$переменных'', потому что они имеют больше общего с $globals'', чем с
@ivars''. Так будет меньше путаницы. Другие пошли дальше и предлагают просто удалить их из языка).
В Ruby нет переменных класса в том смысле, в каком они есть, скажем, в Java (где они называются статическими полями). Ему не нужны переменные класса, потому что классы - это тоже объекты, и поэтому они могут иметь экземплярные переменные, как и любой другой объект. Все, что вам нужно сделать, это удалить лишние @
. (И вам придется предоставить метод доступа для переменной экземпляра класса).
class A
def self.init config
@config = config
end
def self.config # This is needed for access from outside
@config
end
def config
self.class.config # this calls the above accessor on self's class
end
end
Давайте немного упростим это, поскольку A.config
явно является просто считывателем атрибутов:
class A
class << self
def init config
@config = config
end
attr_reader :config
end
def config
self.class.config
end
end
И, на самом деле, A.init
- это просто писатель с забавным именем, поэтому давайте переименуем его в A.config=
и сделаем его писателем, что в свою очередь означает, что наша пара методов теперь просто пара аксессоров. (Поскольку мы изменили API, тестовый код также должен измениться, очевидно).
class A
class << self
attr_accessor :config
end
def config
self.class.config
end
end
class B < A; end
class C < A; end
B.config = "bar"
p B.new.config # => "bar"
p C.new.config # => nil
C.config = "foo"
p B.new.config # => "bar"
p C.new.config # => "foo"
Однако я не могу отделаться от ощущения, что есть что-то более фундаментально неправильное в дизайне, если вам это вообще нужно.