Ruby: Наследовать код, который работает с переменными класса

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

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

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 own @@config

C.init "foo"
p B.new.config  # => "foo" - which would still be "bar" if C had it 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

Теперь мне ясно, почему это происходит, но я не уверен, почему это так.

Не может ли он работать и другим способом, удерживая переменную класса в контексте подкласса?

То, что я также обнаружил раздражающим, было фактом, что я всегда является подклассом, даже когда он называется "в" суперклассе. Из этого я сначала ожидал, что код из суперкласса "выполняется в контексте" подкласса.

Некоторое просвещение об этом было бы весьма полезно.

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

Есть ли способ "мета" для этого? (Я пробовал с class_variable_set и т.д., Но не повезло)

Или, может быть, вся идея в том, что метод "init" ошибочен в первую очередь и есть какой-то другой "шаблон" для этого?

Я мог бы просто сделать @@config хешем, держа все конфиги и всегда выбирать правильный, но я нахожу, что немного неудобно.. (не наследование там решить эту проблему?;)

Ответ 1

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

Ruby не имеет переменных класса в том смысле, что, скажем, Java (где они называются статическими полями) имеет их. Ему не нужны переменные класса, потому что классы также являются объектами, поэтому они могут иметь переменные экземпляра, как и любой другой объект. Все, что вам нужно сделать, это удалить посторонние @ 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 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"

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