@@是不推荐使用的

因为class variables are bound at compile-time

Like unqualified constants, class variables are bound to your current scope when the .rb file is being parsed.
This will probably trip you up when you are writing a module or trait that defines class variables for other classes.

Say you have the following code:

module Mod
  def foo
    @@foo
  end
end

class Klass
  include Mod
end


@@foo does not mean Klass::@@foo (qualifier added to make my point; it is not valid Ruby expression). It actually means Mod::@@foo and is shared between Mod, all classes including Mod as well as all their subclasses.

Also note that only the class and module keywords can be used to change the scope to which class variables are bound. You can not change scope by using class_eval or instance_eval. For instance, the code below declares a variable @@counter that is shared between all classes of your application:

ActiveRecord::Base.class_eval do
  @@counter = 1
end


When encountering such code, Ruby will warn you:

warning: class variable access from toplevel


Unlike constants, which you can qualify like Klass::CONSTANT, there is no way to qualify a class variable like Klass::@@variable. When you want to tell Ruby that you mean another scope than the current scope, you need to use class_variable_get and class_variable_set.

替代1:类的实例变量

缺点就是无法让子类自动查找到

替代2:hanami util的class_attribute

缺点,它是通过inherited的hook来将当前变量值传给子类,因而如果父类本身需要另外一些inherited逻辑,就比较麻烦

摘自utils/lib/hanami/utils/class_attribute.rb(de705e02b83f52df4edb4e9e37c79d053acb99da):

require 'set'
require 'hanami/utils/duplicable'

module Hanami
  module Utils
    # Inheritable class level variable accessors.
    # @since 0.1.0
    #
    # @see Hanami::Utils::ClassAttribute::ClassMethods
    module ClassAttribute
      def self.included(base)
        base.extend ClassMethods
      end

      # @since 0.1.0
      # @api private
      module ClassMethods
        # Defines a class level accessor for the given attribute(s).
        #
        # A value set for a superclass is automatically available by their
        # subclasses, unless a different value is explicitely set within the
        # inheritance chain.
        #
        # @param attributes [Array] a single or multiple attribute name(s)
        #
        # @return [void]
        #
        # @since 0.1.0
        #
        # @example
        #   require 'hanami/utils/class_attribute'
        #
        #   class Vehicle
        #     include Hanami::Utils::ClassAttribute
        #     class_attribute :engines, :wheels
        #
        #     self.engines = 0
        #     self.wheels  = 0
        #   end
        #
        #   class Car < Vehicle
        #     self.engines = 1
        #     self.wheels  = 4
        #   end
        #
        #   class Airplane < Vehicle
        #     self.engines = 4
        #     self.wheels  = 16
        #   end
        #
        #   class SmallAirplane < Airplane
        #     self.engines = 2
        #     self.wheels  = 8
        #   end
        #
        #   Vehicle.engines # => 0
        #   Vehicle.wheels  # => 0
        #
        #   Car.engines # => 1
        #   Car.wheels  # => 4
        #
        #   Airplane.engines # => 4
        #   Airplane.wheels  # => 16
        #
        #   SmallAirplane.engines # => 2
        #   SmallAirplane.wheels  # => 8
        def class_attribute(*attributes)
          singleton_class.class_eval do
            attr_accessor(*attributes)
          end

          class_attributes.merge(attributes)
        end

        protected

        # @see Class#inherited
        def inherited(subclass)
          class_attributes.each do |attr|
            value = send(attr)
            value = Duplicable.dup(value)
            subclass.class_attribute attr
            subclass.send("#{attr}=", value)
          end

          super
        end

        private

        # Class accessor for class attributes.
        # @private
        def class_attributes
          @class_attributes ||= Set.new
        end
      end
    end
  end
end


替代3:active support的class_attribute

缺点:没明显缺点,就是有点复杂

摘自activesupport-5.0.0.1/lib/active_support/core_ext/class/attribute.rb:

class Class
  # 因是定义类的单例类的实例方法,所以子类也能访问到
  #
  #   class Base
  #     class_attribute :setting
  #   end
  #
  #   class Subclass < Base
  #   end
  #
  #   Base.setting = true
  #   Subclass.setting            # => true
  #   Subclass.setting = false
  #   Subclass.setting            # => false
  #   Base.setting                # => true
  #
  # 修改内容的话,会影响父类,因子类的单例类继承自父类的单例类
  # 而父类的单例类的实例方法的定义有闭包
  #
  #   Base.setting = []
  #   Base.setting                # => []
  #   Subclass.setting            # => []
  #
  #   # Appending in child changes both parent and child because it is the same object:
  #   Subclass.setting << :foo
  #   Base.setting               # => [:foo]
  #   Subclass.setting           # => [:foo]
  #
  #   除非重新赋值
  #
  #   Base.setting = []
  #   Subclass.setting += [:foo]
  #   Base.setting               # => []
  #   Subclass.setting           # => [:foo]
  #
  # For convenience, an instance predicate method is defined as well.
  # To skip it, pass instance_predicate: false.
  #
  #   Subclass.setting?       # => false
  #
  # 实例会先探测自己是否有该名字的实例变量,因此,可重写而不影响类变量
  #
  #   Base.setting = true
  #   object = Base.new
  #   object.setting          # => true
  #   object.setting = false
  #   object.setting          # => false
  #   Base.setting            # => true
  #
  # To opt out of the instance reader method, pass instance_reader: false.
  #
  #   object.setting          # => NoMethodError
  #   object.setting?         # => NoMethodError
  #
  # To opt out of the instance writer method, pass instance_writer: false.
  #
  #   object.setting = false  # => NoMethodError
  #
  # To opt out of both instance methods, pass instance_accessor: false.
  def class_attribute(*attrs)
    options = attrs.extract_options!

    # 默认带有,除非设false
    instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
    instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
    instance_predicate = options.fetch(:instance_predicate, true)

    attrs.each do |name|

      # 定义class_reader
      remove_possible_singleton_method(name)
      define_singleton_method(name) { nil }

      # 定义类class_reader?
      remove_possible_singleton_method("#{name}?")
      define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate

      ivar = "@#{name}"

      # 定义类writer,其做法是:
      # 当被调用时,重定义class_reader
      remove_possible_singleton_method("#{name}=")
      define_singleton_method("#{name}=") do |val|
        singleton_class.class_eval do
          remove_possible_method(name)
          define_method(name) { val }
        end

        # 如果是这样调用:
        #   class << A; class_attribute :v; end
        # 则只有在:
        #   class << A; self.v = 123; end
        # 才能从A.v访问到,因调用"#{name}="后才会定义reader
        # 对于一般对象:
        #   class << A; class_attribute :v; end
        # 也同理
        # 不过,有人这样用吗……
        if singleton_class?
          class_eval do
            remove_possible_method(name)
            define_method(name) do
              if instance_variable_defined? ivar
                instance_variable_get ivar
              else
                singleton_class.send name
              end
            end
          end
        end
        val
      end

      # 因instance_writer用的是attr_writer,修改实例变量
      # 所以需先检查实例变量,没有的话,才用类变量
      if instance_reader
        remove_possible_method name
        define_method(name) do
          if instance_variable_defined?(ivar)
            instance_variable_get ivar
          else
            self.class.public_send name
          end
        end

        remove_possible_method "#{name}?"
        define_method("#{name}?") { !!public_send(name) } if instance_predicate
      end

      if instance_writer
        remove_possible_method "#{name}="
        attr_writer name
      end
    end
  end
end