【问题标题】:Ruby class instance variable vs. class variableRuby 类实例变量与类变量
【发布时间】:2013-03-24 07:18:43
【问题描述】:

我读过https://stackoverflow.com/questions/826734/when-do-ruby-instance-variables-get-set,但我对何时使用类实例变量有两种看法。

类变量由一个类的所有对象共享,实例变量属于一个对象。如果我们有类变量,就没有多少空间可以使用类实例变量了。

有人能解释一下这两者之间的区别以及何时使用它们吗?

这是一个代码示例:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

更新:我现在明白了!类实例变量不沿继承链传递。

【问题讨论】:

    标签: ruby instance-variables class-variables class-instance-variables


    【解决方案1】:

    我相信主要(唯一?)不同的是继承:

    class T < S
    end
    
    p T.k
    => 23
    
    S.k = 24
    p T.k
    => 24
    
    p T.s
    => nil
    

    类变量由所有“类实例”(即子类)共享,而类实例变量仅特定于该类。但是,如果您从不打算扩展您的课程,则差异纯粹是学术上的。

    【讨论】:

    • 这不是唯一的区别。 “共享”与“实例”比继承更进一步。如果您放置实例吸气剂,您将获得S.new.s =&gt; nilS.new.k =&gt; 23
    【解决方案2】:

    类的实例变量:

    class Parent
      @things = []
      def self.things
        @things
      end
      def things
        self.class.things
      end
    end
    
    class Child < Parent
      @things = []
    end
    
    Parent.things << :car
    Child.things  << :doll
    mom = Parent.new
    dad = Parent.new
    
    p Parent.things #=> [:car]
    p Child.things  #=> [:doll]
    p mom.things    #=> [:car]
    p dad.things    #=> [:car]
    

    类变量:

    class Parent
      @@things = []
      def self.things
        @@things
      end
      def things
        @@things
      end
    end
    
    class Child < Parent
    end
    
    Parent.things << :car
    Child.things  << :doll
    
    p Parent.things #=> [:car,:doll]
    p Child.things  #=> [:car,:doll]
    
    mom = Parent.new
    dad = Parent.new
    son1 = Child.new
    son2 = Child.new
    daughter = Child.new
    
    [ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
    #=> [:car, :doll]
    #=> [:car, :doll]
    #=> [:car, :doll]
    #=> [:car, :doll]
    #=> [:car, :doll]
    

    使用类上的实例变量(而不是该类的实例),您可以存储该类共有的内容,而无需子类也自动获取它们(反之亦然)。使用类变量,您可以方便地不必从实例对象写入self.class,并且(如果需要)您还可以在整个类层次结构中自动共享。


    将这些合并到一个示例中,该示例还涵盖了实例上的实例变量:

    class Parent
      @@family_things = []    # Shared between class and subclasses
      @shared_things  = []    # Specific to this class
    
      def self.family_things
        @@family_things
      end
      def self.shared_things
        @shared_things
      end
    
      attr_accessor :my_things
      def initialize
        @my_things = []       # Just for me
      end
      def family_things
        self.class.family_things
      end
      def shared_things
        self.class.shared_things
      end
    end
    
    class Child < Parent
      @shared_things = []
    end
    

    然后在行动:

    mama = Parent.new
    papa = Parent.new
    joey = Child.new
    suzy = Child.new
    
    Parent.family_things << :house
    papa.family_things   << :vacuum
    mama.shared_things   << :car
    papa.shared_things   << :blender
    papa.my_things       << :quadcopter
    joey.my_things       << :bike
    suzy.my_things       << :doll
    joey.shared_things   << :puzzle
    suzy.shared_things   << :blocks
    
    p Parent.family_things #=> [:house, :vacuum]
    p Child.family_things  #=> [:house, :vacuum]
    p papa.family_things   #=> [:house, :vacuum]
    p mama.family_things   #=> [:house, :vacuum]
    p joey.family_things   #=> [:house, :vacuum]
    p suzy.family_things   #=> [:house, :vacuum]
    
    p Parent.shared_things #=> [:car, :blender]
    p papa.shared_things   #=> [:car, :blender]
    p mama.shared_things   #=> [:car, :blender]
    p Child.shared_things  #=> [:puzzle, :blocks]  
    p joey.shared_things   #=> [:puzzle, :blocks]
    p suzy.shared_things   #=> [:puzzle, :blocks]
    
    p papa.my_things       #=> [:quadcopter]
    p mama.my_things       #=> []
    p joey.my_things       #=> [:bike]
    p suzy.my_things       #=> [:doll] 
    

    【讨论】:

    • @Phronz 你在代码中提到的 self.things 和 self.class.things 有什么区别?
    • @cyborg self.things 在当前范围内引用了一个方法things(如果是类的实例,它将是实例的方法),其中self.class.things 引用了一个things当前范围的类中的方法(同样,如果是类的实例,则表示类方法)。
    • 美丽的解释。
    【解决方案3】:

    Source

    实例方法的可用性

    • 类实例变量仅对类方法可用,对实例方法不可用。
    • 类变量可用于实例方法和类方法。

    可继承性

    • 类实例变量在继承链中丢失。
    • 类变量不是。
    class Vars
    
      @class_ins_var = "class instance variable value"  #class instance variable
      @@class_var = "class variable value" #class  variable
    
      def self.class_method
        puts @class_ins_var
        puts @@class_var
      end
    
      def instance_method
        puts @class_ins_var
        puts @@class_var
      end
    end
    
    Vars.class_method
    
    puts "see the difference"
    
    obj = Vars.new
    
    obj.instance_method
    
    class VarsChild < Vars
    
    
    end
    
    VarsChild.class_method
    

    【讨论】:

      【解决方案4】:

      正如其他人所说,类变量在给定类及其子类之间共享。类实例变量只属于一个类;它的子类是独立的。

      为什么会存在这种行为?好吧,Ruby 中的一切都是对象——甚至是类。这意味着每个类都有一个与之对应的类Class(或者更确切地说,Class 的子类)的对象。 (当你说 class Foo 时,你实际上是在声明一个常量 Foo 并为其分配一个类对象。)每个 Ruby 对象都可以有实例变量,所以类对象也可以有实例变量。

      问题在于,类对象上的实例变量的行为方式并不像您通常希望类变量的行为方式那样。您通常希望在超类中定义的类变量与其子类共享,但这不是实例变量的工作方式——子类有自己的类对象,而该类对象有自己的实例变量。因此,他们引入了具有您更可能想要的行为的单独的类变量。

      换句话说,类实例变量是 Ruby 设计的一种意外。你可能不应该使用它们,除非你明确知道它们就是你要找的东西。

      【讨论】:

      • 所以类变量就像Java中的静态变量?
      【解决方案5】:

      虽然使用类变量似乎很有用,但由于类变量在子类之间共享,并且可以在单例方法和实例方法中引用,但有一个明显的缺点。它们是共享的,因此子类可以更改类变量的值,而基类也会受到更改的影响,这通常是不受欢迎的行为:

      class C
        @@c = 'c'
        def self.c_val
          @@c
        end
      end
      
      C.c_val
       => "c" 
      
      class D < C
      end
      
      D.instance_eval do 
        def change_c_val
          @@c = 'd'
        end
      end
       => :change_c_val 
      
      D.change_c_val
      (irb):12: warning: class variable access from toplevel
       => "d" 
      
      C.c_val
       => "d" 
      

      Rails 引入了一个方便的方法,称为 class_attribute。顾名思义,它声明了一个类级属性,其值可由子类继承。 class_attribute 值可以在单例和实例方法中访问,就像类变量一样。但是,在 Rails 中使用 class_attribute 的巨大好处是子类可以更改自己的值,并且不会影响父类。

      class C
        class_attribute :c
        self.c = 'c'
      end
      
       C.c
       => "c" 
      
      class D < C
      end
      
      D.c = 'd'
       => "d" 
      
       C.c
       => "c" 
      

      【讨论】:

      • 好电话,我以前没用过这个。尽管您需要确保每次要访问属性c 时都添加self.,但它似乎有效,例如self.c。文档说default: 参数可以传递给class_attribute,但由于我刚刚提到self 的观点,它似乎不起作用。
      • 当您说“虽然使用类实例变量可能立即有用”时,我认为您的意思是“类变量”,而不是“类实例变量”,对吗?(参见ruby-lang.org/en/documentation/faq/8/.
      • 是的,这个答案完全混淆了“类实例变量”和“类变量”,这是问题的重点。
      【解决方案6】:

      Official Ruby FAQ: What is the difference between class variables and class instance variables?

      主要区别在于继承的行为:类变量在一个类及其所有子类之间共享,而类实例变量只属于一个特定的类。

      类变量在某种程度上可以看作是继承层次结构中的全局变量,具有全局变量带来的所有问题。例如,一个类变量可能(意外地)被它的任何子类重新分配,影响所有其他类:

      class Woof
      
        @@sound = "woof"
      
        def self.sound
          @@sound
        end
      end
      
      Woof.sound  # => "woof"
      
      class LoudWoof < Woof
        @@sound = "WOOF"
      end
      
      LoudWoof.sound  # => "WOOF"
      Woof.sound      # => "WOOF" (!)
      

      或者,一个祖先类可能稍后被重新打开和更改,可能会产生令人惊讶的效果:

      class Foo
      
        @@var = "foo"
      
        def self.var
          @@var
        end
      end
      
      Foo.var  # => "foo" (as expected)
      
      class Object
        @@var = "object"
      end
      
      Foo.var  # => "object" (!)
      

      因此,除非您确切知道自己在做什么并且明确需要这种行为,否则最好使用类实例变量。

      【讨论】:

        【解决方案7】:

        对于那些具有 C++ 背景的人,您可能有兴趣与 C++ 等价物进行比较:

        class S
        {
        private: // this is not quite true, in Ruby you can still access these
          static int    k = 23;
          int           s = 15;
        
        public:
          int get_s() { return s; }
          static int get_k() { return k; }
        
        };
        
        std::cerr << S::k() << "\n";
        
        S instance;
        std::cerr << instance.s() << "\n";
        std::cerr << instance.k() << "\n";
        

        正如我们所见,k 是一个类似static 的变量。这 100% 类似于全局变量,只是它由类拥有作用域 是正确的)。这使得更容易避免类似命名的变量之间的冲突。与任何全局变量一样,该变量只有一个实例,并且对其进行修改始终对所有人可见。

        另一方面,s 是一个特定于对象的值。每个对象都有自己的值实例。在 C++ 中,您必须创建一个实例才能访问该变量。在 Ruby 中,类定义本身就是类的一个实例(在 JavaScript 中,这称为原型),因此您可以从类中访问 s 而无需额外的实例化。类实例可以修改,但s 的修改将特定于每个实例(S 类型的每个对象)。所以修改一个不会改变另一个的值。

        【讨论】:

          【解决方案8】:

          简单示例展示

          • 类变量可继承性
          • 封装 类实例变量

          注意:class &lt;&lt; self 的使用很方便,而不是必须在此块内的所有方法前面加上 self. 注意:class &lt;&lt; self 修改了 self,因此它指向 元类 代表Parent(见https://stackoverflow.com/a/38041660/960184

          示例代码

          class Parent
            class << self
              attr_reader :class_instance_var
          
              def class_instance_var=(value)
                @class_instance_var="set by #{self.name} to #{value}"
              end
          
              def class_var
                @@class_var
              end
          
              def class_var=(value)
                @@class_var = "set by #{self.name} to #{value}"
              end
            end
          end
          
          class Child < Parent
          end
          
          # use the instance separately in parent and subclass
          puts "\n* Exercising class instance variable setters
          * Setting Parent and Child class instance variables differently
          * Parent.class_instance_var = 1000\n* Child.class_instance_var = 2000\n\n"
          
          Parent.class_instance_var = 1000
          Child.class_instance_var = 2000
          puts "Parent.class_instance_var=(#{Parent.class_instance_var})"
          puts "Child.class_instance_var=(#{Child.class_instance_var})"
          
          # set class variable in via parent (changes both in parent and subclass)
          puts "\n* Exercising Parent class variable setter
          * Set class variable value to 3000 using parent, it changes in Child also
          * Parent.class_var = 3000\n\n"
          
          Parent.class_var = 3000
          puts "Parent.class_var=(#{Parent.class_var})"
          puts "Child.class_var=(#{Child.class_var})"
          
          # set class variable in via subclass (changes both in parent and subclass)
          puts "\n* Exercising Child class variable setter
          * Set class variable value to 5000 using child, it changes in Parent also
          * Child.class_var = 5000\n\n"
          
          Child.class_var = 5000
          puts "Parent.class_var=(#{Parent.class_var})"
          puts "Child.class_var=(#{Child.class_var})"
          
          

          使用 ruby​​ v3.0.2 输出

          * Exercising class instance variable setters
          * Setting Parent and Child class instance variables differently
          * Parent.class_instance_var = 1000
          * Child.class_instance_var = 2000
          
          Parent.class_instance_var=(set by Parent to 1000)
          Child.class_instance_var=(set by Child to 2000)
          
          * Exercising Parent class variable setter
          * Set class variable value to 3000 using parent, it changes in Child also
          * Parent.class_var = 3000
          
          Parent.class_var=(set by Parent to 3000)
          Child.class_var=(set by Parent to 3000)
          
          * Exercising Child class variable setter
          * Set class variable value to 5000 using child, it changes in Parent also
          * Child.class_var = 5000
          
          Parent.class_var=(set by Child to 5000)
          Child.class_var=(set by Child to 5000)
          

          【讨论】:

            猜你喜欢
            • 2014-09-21
            • 2021-07-29
            • 1970-01-01
            • 2013-11-26
            • 1970-01-01
            • 1970-01-01
            • 2015-08-13
            • 2010-10-28
            • 2011-02-12
            相关资源
            最近更新 更多