Ruby类示例变量与类变量

ruyhziif  于 2023-04-05  发布在  Ruby
关注(0)|答案(8)|浏览(140)

我读过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

**更新:**我明白了!类示例变量不是沿着继承链传递的。

ccrfmcuu

ccrfmcuu1#

类上的示例变量:

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]
xbp102n0

xbp102n02#

来源

示例方法可用性

  • 类示例变量仅可用于类方法,而不可用于示例方法。
  • 类变量可用于示例方法和类方法。
    可继承性
  • 类示例变量在继承链中丢失。
  • 类变量不是。
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
9njqaruj

9njqaruj3#

我认为主要的(唯一的?)不同是继承:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

类变量由所有的“类示例”(即子类)共享,而类示例变量只特定于该类。但是如果你从来不打算扩展你的类,那么这种区别纯粹是学术上的。

93ze6v8z

93ze6v8z4#

正如其他人所说,类变量在给定的类和它的子类之间共享。它的子类是分开的。
为什么会存在这种行为呢?Ruby中的所有东西都是对象,甚至类也是。这意味着每个类都有一个类Class的对象(或者更确切地说,Class的子类)。(当你说class Foo时,你实际上是声明了一个常量Foo,并将一个类对象赋给它。)每个Ruby对象都可以有示例变量,所以类对象也可以有示例变量。
麻烦的是,类对象上的示例变量并不真正像你通常希望的那样表现。你通常希望在超类中定义的类变量与它的子类共享,但这不是示例变量的工作方式-子类有自己的类对象,而这个类对象有它自己的示例变量。所以他们引入了单独的类变量,它们的行为更可能是你想要的。
换句话说,类示例变量是Ruby设计的一个意外,除非你明确知道它们是你要找的,否则你可能不应该使用它们。

hrirmatl

hrirmatl5#

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" (!)

所以,除非你确切地知道你在做什么,并且明确地需要这种行为,否则你最好使用类示例变量。

fumotvh3

fumotvh36#

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

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值可以在单例和示例方法中访问,就像class变量一样。然而,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"
gg0vcinb

gg0vcinb7#

对于那些有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%类似于一个全局变量,除了它由类 * 拥有 *(正确的是 scoped)。这使得更容易避免类似命名的变量之间的冲突。像任何全局变量一样,该变量只有一个示例,并且修改它总是可见的。
另一方面,s是一个特定于对象的值。每个对象都有自己的值示例。在C++中,必须创建一个示例才能访问该变量。在Ruby中,类定义本身就是类的示例(在JavaScript中,这被称为原型),因此,您可以从类访问s,而无需额外的示例化。类示例可以被修改,但是对s的修改将是特定于每个示例(每个S类型的对象)的。所以修改一个示例不会改变另一个示例的值。

dxxyhpgq

dxxyhpgq8#

简单示例展示

类变量的可继承性*
类示例变量的封装*

注意:使用class << self是一种方便,而不是必须在此块中的所有方法前面加上self.注意:class << 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)

相关问题