Ruby: наследовать код, который работает с переменными класса - PullRequest
42 голосов
/ 09 августа 2009

Ситуация: у меня есть несколько классов, каждый из которых должен содержать переменную с конфигурационным хешем; различный хеш для каждого класса, но одинаковый для всех экземпляров класса.

Сначала я попробовал вот так

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 хешем, содержащим все конфиги, и всегда выбирать правильный, но я нахожу это немного неловким ... (разве нет наследования для решения такого рода проблем?;)

1 Ответ

106 голосов
/ 09 августа 2009

@@variables не являются переменными класса. Они являются переменными иерархии классов , то есть они совместно используются всей иерархией классов, включая все подклассы и все экземпляры всех подклассов. (Было предложено думать о @@variables больше как о $$variables, потому что они на самом деле имеют больше общего с $globals, чем с @ivars. Таким образом, меньше путаницы. Другие пошли дальше и предполагают, что они следует просто удалить из языка.)

В Ruby нет переменных класса в том смысле, что, скажем, в Java (где они называются статическими полями) они есть. Для не нужны переменные класса, поскольку классы также являются объектами, и поэтому они могут иметь переменные instance , как и любой другой объект. Все, что вам нужно сделать, это удалить посторонние @ s. (И вам нужно будет предоставить метод доступа для переменной экземпляра класса.)

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 явно является только attribute_reader:

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"

Однако я не могу избавиться от ощущения, что в дизайне есть что-то более принципиальное, если вам это вообще нужно.

...