我读了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
更新:我现在明白了!类实例变量不会沿着继承链传递。
简单示例显示
类变量的继承性
类实例变量的封装
注意:使用类<< self是一种方便,而不是必须在这个块中的所有方法前面加上self。注意:类<< self修改self,因此它指向父类的元类(参见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)
虽然利用类变量看起来很有用,但由于类变量在子类之间共享,并且它们可以在单例方法和实例方法中引用,因此有一个显著的缺点。它们是共享的,所以子类可以改变类变量的值,基类也会受到这个变化的影响,这通常是不可取的行为:
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"
正如其他人所说,类变量在给定的类及其子类之间共享。类实例变量只属于一个类;它的子类是分开的。
为什么会有这种行为?Ruby中的所有东西都是对象——甚至是类。这意味着每个类都有一个class类的对象(或者更确切地说,class的子类)对应于它。(当你说类Foo时,你实际上是在声明一个常量Foo并将一个类对象赋值给它。)每个Ruby对象都可以有实例变量,所以类对象也可以有实例变量。
问题是,类对象上的实例变量的行为并不像您通常希望的类变量的行为那样。通常,您希望在超类中定义的类变量与其子类共享,但实例变量不是这样工作的——子类有自己的类对象,而类对象有自己的实例变量。所以他们引入了独立的类变量来区分你更想要的行为。
换句话说,类实例变量在某种程度上是Ruby设计的一个意外。你可能不应该使用它们,除非你明确知道它们是你要找的东西。
Ruby官方常见问题:类变量和类实例变量之间有什么区别?
主要的区别是有关继承的行为:类变量在类及其所有子类之间共享,而类实例变量只属于一个特定的类。
在某种程度上,类变量可以被视为继承层次结构上下文中的全局变量,而全局变量会带来各种问题。例如,一个类变量可能(意外地)被它的任何子类重新赋值,影响所有其他类:
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" (!)
因此,除非您确切地知道自己在做什么并且显式地需要这种行为,否则最好使用类实例变量。