Ruby 正确理解类变量

liy_j · 2013年08月07日 · 最后由 liy_j 回复于 2013年08月16日 · 4154 次阅读

测试环境是 ruby1.8.7。ruby1.9,ruby2.0 版本不适用 各位看官,请看回复中的用于 ruby1.9 和 2.0 的帖子 NOTE:类变量并不真正属于类,它们属于类体系结构,可以被类或类的实例所使用。(类变量必须被初始化) 场景一:

#   代码a
@@v = 1                          
class MyClass                 
 @@v                             
end                             

代码 a 中,@@v = 1 定义于 main 对象的上下文,因此它属于 main 的类 Object,所以也属于 Object 的所有子类。类 MyClass 继承自 Object,因此也共享了这个类变量

#   代码b
@@v = 1                          
class MyClass                 
 @@v =2                            
end     
p @@v #=>2                        

代码 b 中,类 MyClass 修改了共享类变量 @@v,因此 Object 中类变量值也被改变了。

#   代码c                    
class MyClass                 
 @@v                            
end      
@@v = 1                       

代码 c 中,执行这个文件时,会报没有初始化类变量 @@v 的错误,也就是说,在类 MyClass 的祖先链中未发现已定义的 @@v,因此在类 MyClass 中开始定义 @@v,并且必须被初始化

#   代码d                      
class MyClass                 
 @@v =2                            
end      
@@v = 1    
Object.class_variable_get :@@v   #=>1                

代码 d 是代码 c 的进化版本,由于在类 MyClass 的祖先链中未发现已定义的 @@v,因此在类 MyClass 中定义的 @@v = 2 属于该类及所有子类,且可以被该类的实例所使用。 代码 d 中在类下方定义了 @@v=1,这里的 @@v=1 属于 Object 可以被其子类共享,与 MyClass 中 @@v 是两个不同的类变量。Object 中类变量 @@v=1 不能被 MyClass 及其子类所共享。 从代码 d 中可以看到,MyClass 中 @@v 的改变不会影响 Object 中类变量(因为 MyClass 中类变量不是共享于 Object 的类变量)。也可以通过 MyClass 的子类来验证

#   代码e 
@@v = 1                    
class MyClass                 
  @@v =2 
  def my_method
    p @@v
  end                           
end      
MyClass.new.my_method #=> 2  
p  @@v #=>2

代码 e 中,MyClass 中 @@v 是共享于 Object 中类变量(@@v 定义于 Object),因此 MyClass 中 @@v 的改变会影响定义于 Object 中 @@v 的值

#   代码f                    
class MyClass                 
  @@v =2 
  def my_method
    p @@v
  end                           
end  
@@v = 1    
MyClass.new.my_method #=> 2  
p  @@v #=>1

代码 f 中,MyClass 中 @@v=2 与 Object 中 @@v=1 是两个不同的类变量,不存在共享的关系,而是分别定义于不同的类中。因此 @@v 的改变不会影响彼此

#   代码g                 
class MyClass                 
  def my_method
     p @@v
     @@v = 2 #修改共享类变量值
  end                           
end  
@@v = 1    
MyClass.new.my_method #=> 1  
p  @@v #=>2

代码 g 中,MyClass 的方法 my_method 中 @@v 属于共享的类变量,共享于 Object 中的 @@v,因此在 my_method 中修改 @@v 的值会影响定义于 Object 中 @@v 的值。(定义于方法中的代码,只有在方法调用时才执行)

场景二

#  f1.rb
module M1
  @@via = "M1"
  def self.m1
    p @@via
  end
end
#  f2.rb
module M2
  @@via = "M2"
  def self.m2
    p @@via
  end
end
#   图1-1
@@via = "object"
require "f1.rb"
require "f2.rb"
class MyClass
  include M1
  include M2
  def my_method
    p @@via
  end
end
MyClass.new.my_method #=> "M2"
p @@via #=>"object"
M1.m1 #=> "M1"
M2.m2 #=> "M2"
#   图1-2
@@via = "object"
require "f1.rb"
require "f2.rb"
class MyClass
  include M1
  include M2
  @@via = "myclass"
  def my_method
    p @@via
  end
end
MyClass.new.my_method #=> "myclass"
p @@via #=>"object"
M1.m1 #=> "M1"
M2.m2 #=> "myclass"

图 1-1 和图 1-2 中分别在 M1、M2 和 Object 中定义了 @@via,这三个 @@via 是三个不同的类变量。Object 中定义的 @@via 可以被其子类 MyTest 共享;M1 中定义的 @@via 可以被 include 的类共享;M2 中定义的 @@via 也可以被 include 的类共享;那么类 MyTest 中共享的 @@via 应该是哪个类或模块中定义的 @@via? 通过图 1-1 和图 1-2 的例子可以理解为: 类也是按照祖先链的方向来查找定义 @@via 的类或模块,修改共享 @@via 的值则会影响首次查找到的定义 @@via 的类或模块中 @@via 的值。 若在类的祖先链上未查找到有定义 @@via 的类或模块,则类中显示 @@via 的地方所表示的意思为:在此处定义 @@via 且必须初始化(非共享的类变量)

不错,谢谢分享. 辛苦了

好文,受教了~

很久不见的纯技术分析. 我补充下关于类变量的看法, 它在 Ruby 中是一个不好的设计之一, 一般时候避免使用它的继承性, 而使用类实例变量. 即:

class A
  @a = 1

  def get_a
    self.class.get_a
  end

  def self.get_a
    @a
  end
end
4 楼 已删除

#3 楼 @lyfi2003 我觉得不是不好。只是不容易理解。类变量还是有其适用的环境的。

最外层就是所有类的超类,写个类变量就成了大家共享的东西,类这个概念在 ruby 里太深入了

代码 f 有错误,我用的 Ruby 2.0.0-p247。

#   代码f                    
class MyClass                 
  @@v =2 
  def my_method
    p @@v
  end                           
end  
@@v = 1    
MyClass.new.my_method #=> 1,而不是2  
p  @@v #=>1

我个人的理解,代码 f 中,MyClass 中 @@v=2 与 Object 中 @@v=1 是同一个类变量

@hanluner ,@hxtheone ,@lyfi2003 ,@mingyuan0715 ,@kenshin54 ,@dddd1919 ,@lifuzho 这是用 1.8.7 版本测试,悲催啊,俺们公司一直在用 1.8.7。不过 1.8.7 和以后的版本源码中类变量的查找顺序也是发生了变化,1.8.7 是按祖先链的方向查找,而以后的版本则是反向查找。觉着 1.8.7 以后的版本更符合所谓类体系结构。昨天忙,没有来得及修改,稍后会上传,最新的。

测试所用版本:Ruby 1.9.3-p448;Ruby 2.0.0-p247 定义的类变量(非共享)存储在类对象的实例变量表中,当查找类变量时,就沿着类体系结构(祖先链的反向)中类对象或模块对象的实例变量表中查找。

module M1
  @@via = "m1"
end 

module M2
@@via = "m2"
end

class MyClass
  include M1
  include M2
  @@via = "myclass"
end

对于类体系结构的理解:当这行该文件时,依次记录了 MyClass 所继承的类和包含的模块,即:继承于 Object,依次包含的模块为:M1、M2,这就是 MyClass 的类体系结构:Object—>M1—>M2 NOTE: RUBY1.9 中 Module#class_variables 和 RUBY2.0 中 Module#class_variables 的参数是不同的,ruby2.0 带有参数(详见 API)。 为了方便描述先定义下概念:定义的类变量和共享的类变量 在类中定义而非共享的类变量称作为:定义的类变量;而从类体系结构中共享的类变量称之为:共享的类变量。

@@via = "object"
class MyClass
  @@via
  def my_method
    p @@via
  end
end
Object.class_variables(false)  #=> [:@@via]
Object.class_variable_get :@@via #=> "object"

MyClass.class_variables #=> []    ruby1.9
MyClass.class_variables(false) #=> []   ruby2.0
MyClass.new.my_method  #=> "object"

Object.remove_calss_variable(:@@via) #=> "object" 删除定义的类变量
MyClass.new.my_method #会报类变量没有初始化的错误

                                       a
@@via = "object"
class MyClass
  @@via = "myclass"
  def my_method
    p @@via
  end
end
Object.class_variables(false)  #=> [:@@via]
Object.class_variable_get :@@via #=> "myclass"

MyClass.class_variables #=> []    ruby1.9
MyClass.class_variables(false) #=> []   ruby2.0
MyClass.new.my_method  #=> "myclass"

Object.remove_calss_variable(:@@via) #=> "myclass" 删除定义的类变量
MyClass.new.my_method #会报类变量没有初始化的错误

                                      b

通过上面图 a 和图 b 代码,特别是 MyClass.class_variables 所得的结果,可以看出,类 Object 中定义了 @@via,而类 MyClass 中的 @@via 是共享于 Object 中 @@via。因此 MyClass 中 @@via 属于共享的类变量。 图 a 和图 b 中都同过 Object.remove_calss_variable 删除 Object 中定义的 @@via,因此在 MyClass 的类体系结构中 @@via 不存在,所以再调用 MyClass.new.my_method 时会报类变量没有初始化的错误。 图 a 和图 b 中删除 @@via 的返回值是不一样的(图 a 返回值:object,图中返回值:myclass);因为图 b 中 MyClass 修改了共享的类变量 @@via 的值。

class MyClass
  @@via = "myclass"
  def my_method
    p @@via
  end
end
验证一:
MyClass.class_variables #=> [:@@via]  ruby1.9
MyClass.class_variables(false) #=> [:@@via]  ruby2.0
Object.class_variables(false)  #=> []    
MyClass.new.my_method #=> "myclass"

@@via = "object"

验证二:
MyClass.class_variables #=> [:@@via]  ruby1.9  此处的类变量是MyClass对象的实例变量表中的@@via
MyClass.class_variables(false) #=> [:@@via]  ruby2.0
MyClass.class_variable_get :@@via #=> "object"
Object.class_variables(false)  #=> [:@@via]  此处的类变量是Object对象的实例变量表中的@@via
Object.class_variable_get :@@via #=> "object"
MyClass.new.my_method #=> "object"

验证三:
Object.remove_class_variable(:@@via)
Object.class_variables(false) #=> []

MyClass.class_variables #=> [:@@via]  ruby1.9 此处的类变量是MyClass对象的实例变量表中的
MyClass.class_variables(false) #=> [:@@via]  ruby2.0
MyClass.class_variable_get :@@via #=> "myclass"
MyClass.new.my_method #=> "myclass"

当执行到 @@via = "myclass"这步时,首先会在类体系结构依次查找实例变量表(类对象或模块对象)若在类体系结构中某个类或模块的实例变量表中找到 @@via,那么 MyClass 中 @@via 属于共享的实例变量,在此处表示修改已存在的 @@via 的值;若类体系结构的实例变量表中找不到 @@via,那么 @@via = "myclass"此处表示定义 @@via 并初始化,并将 @@via 及值存储在 MyClass 的实例变量表中。 验证一: MyClass 类体系结构中只存在一个 @@via 且定义于 MyClass 类中。 验证二: NOTE:Module#class_variable_get 方法也是在类的体系结构中查找类变量的 由于在 Object 中定义了 @@via = "object",即:Object 的实例变量表中存储着 @@via。此时 MyClass 中 @@via 和 Object 中 @@via 是两个不同的类变量,分别存储在不同的类的实例变量表中。 方法调用(MyClass.new.my_method)中 @@via 的值会从类体系结构的实例变量表中依次查找,若找到则返回该值;否则会在该方法(my_method)所在的类的实例变量表中查找,若找到则返回值,如果都没有找到的话就会报类变量没有初始化的错误。 验证三: NOTE:在这个地方的验证会出现问题,多执行几次就是正确的结果。也许是 remove_class_variable 这个方法造成的;也许是 ruby 设计的问题吧。分别在 irb 和 netbeans 中执行过。 通过前面的代码,知道 Object 和 MyClass 的实例变量表中分别存储着 @@via;执行 Object.remove_class_variable(:@@via) 这行代码后,Object 实例变量表中 @@via 被删除了,而 MyClass 的实例变量表中 @@via 还存在,因此你执行方法调用 (MyClass.new.my_method) 在类体系结构中没有查找到 @@via,而在 MyClass 实例变量表中找到了 @@via 的值,且返回该值。 从上面删除 @@via 的值也可以看出,MyClass 和 Object 中 @@via 是两个不同的类变量。 当然也可以通过 MyClass.remove_class_variable(:@@via) 来删除 MyClass 的实例变量表中 @@via,来验证这两个类变量是不相同的。

module M1
  @@via = "M1"
  def self.mm1
    remove_class_variable(:@@via)
  end
  def m1
    p @@via
  end
end

module M2
  @@via = "M2"
  def self.mm2
    remove_class_variable(:@@via)
  end
  def m2
    p @@via
  end
end

class MyClass
 include M1
 include M2
 @@via = "myclass" #共享的类变量,修改M1中@@via的值

  def my_method
    p @@via
  end

end
MyClass.ancestors #=> [MyClass, M2, M1, Object, Kernel, BasicObject]

M1.class_variables(false) #=>[:@@via]
M1.class_variable_get :@@via #=>  "myclass"  类变量的值被修改过,因此返回的是修改后值

M2.class_variables(false) #=>[:@@via]
M2.class_variable_get :@@via #=>  "M2"

MyClass.class_variables(false) #=>[ ]
MyClass.new.m1 #=> "myclass"
MyClass.new.m2 #=> "M2"

M1.mm1 #=> "myclass" #删除了M1中@@via

M1.class_variables(false) #=>[ ]
M2.class_variables(false) #=>[:@@via]
M2.class_variable_get :@@via #=>  "M2"
MyClass.class_variables(false) #=>[ ] #没有类变量

MyClass.new.my_method #=> "M2"
MyClass.new.m2 #=> "M2"
MyClass.new.m1 #会报未初始化类变量的错误

NOTE:模块的体系结构就是模块所 include 的模块。模块 M1 和 M2 中都没有 include 模块,因此它们的类体系结构只有本身。 当执行该文件时,分别会在 M1,M2 及 MyClass 的类体系结构中查找是否存在 @@via,若存在则此处 @@via 表示共享的类变量,否则表示在类体系结构中未查找到 @@via,在此处为定义 @@via 且必须初始化。 方法调用中的类变量会在该方法所属的类或模块的体系结构中查找。例如:MyClass.new.m1 调用的是模块 M1 中的方法,则 m1 方法中 @@via 的值就会在 M1 的体系结构中查找。

综上所述: 通过类体系结构 (包括模块) 来依次查找所需的类变量,为了方便记忆可以记为:祖先链的反向查找。(某个类或模块的体系结构包含在祖先链中,是祖先链的一个子集)

需要 登录 后方可回复, 如果你还没有账号请 注册新账号