回到Ruby系列文章
厘清Ruby中类和对象的各种方法、变量 Ruby中有以下一些概念(当然,还有其它类型的变量):
例如,下面代码中出现的一大堆乱七八糟的东西,后文会一一解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class C a = 3 puts "a in C: #{a} " @b = 33 puts "@b in C: #@b" def C .f puts "@b in C.f: #@b" end @@x = 333 puts "@@x in C: #@@x" def m puts "@@x in m: #@@x" end end C.f C.new.m
Ruby局部变量 Ruby中有几种局部环境:
def函数定义开启的作用域
class关键字开启作用域
module关键字开启作用域
语句块或Proc内声明的变量是局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class C a = 3 def f b = 4 end 1 .upto(5 ) {|x | y=0 ;puts x,y} end module aa = 33 end
注意,虽然class和module上下文中定义的局部变量无法被方法访问,但此时可以考虑使用常量:
1 2 3 4 5 6 7 8 9 class A name = "junmajinlong" Name = "junmajinlong" def f puts Name end end A.new.f
实例变量和实例方法 实例变量是每个对象都独立拥有的变量,以@
符号开头,按照常规定义的方式,它只能在实例方法内部被创建(赋值)或被访问(实际上也能在class和module内部创建,见下文)。实例方法是所有对象都共享的属于对象的方法。
子类会继承实例方法,但不会继承实例变量,因为实例方法是共享的,实例变量是每个对象独有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class C def initialize (name, age ) @name = name @age = age end def get_name return @name end end class D < Cend c1 = C.new("junmajinlong" , 23 ) puts c1.get_name d1 = D.new("gaoxiaofang" , 24 ) puts d1.get_name
由于Ruby中的类自身和模块自身也是对象,因此,除了上述在实例方法中创建实例变量的常规方式,还能在class和module内部创建各自的实例变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class A @name = "junmajinlong" puts "in A: #{@name } " def self .m puts "in A.m: #{@name } " end def f self .class .@name end end A.m module M @name = "junmajinlong" puts "in M: #{@name } " def self .m puts "in M.m: #{@name } " end end M.m
类变量 类变量是所有对象都共享的变量,以@@
符号开头,可在类上下文定义,也可以在实例方法内部被定义。
子类会继承类变量,因此子类的类上下文以及子类的实例方法内也可以访问父类的类变量。这是因为类变量的目的是为了让所有该类的对象都能访问该变量,而子类对象也属于父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class C @@num = 0 def initialize @@num += 1 end def how_many return @@num end def C .how_many return @@num end end c1 = C.new c2 = C.new p C.how_many p c1.how_many class D < C puts "in D: #@@num" end p D.how_many
如果想要在class外面访问类变量,可考虑定义一个类变量的getter方法,也可以使用class_eval
进入class上下文,还可使用Module模块提供的class_variable_get()
方法。
1 2 3 4 5 6 class C @@num = 0 end p C.class_eval("@@num" ) p C.class_variable_get "@@num"
类变量会被类自身以及所有子类(可能有兄弟子类)共享,相当于是一种限定在亲缘类中的全局变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class A @@class_var = "class_var in A" puts "in A: #{@@class_var } " def m puts "in A.m: #{@@class_var } " end end A.m class B < A puts "in B: #{@@class_var } " def mm puts "in B.mm: #{@@class_var } " end end
任意一个位置修改类变量,都是全局的修改。所以,Ruby中不建议使用类变量来保存数据。
在Ruby中,更好的方式是使用类的实例变量来替代类变量,因为类的实例变量更容易被控制。关于类的实例变量,下文马上介绍。
1 2 3 4 5 6 7 8 9 10 11 12 13 class C @num = 0 class << self attr_accessor :num end def initialize self .class .num += 1 end end c = C.new p C.num
类(模块)方法、类(模块)的实例变量 在Ruby中,所有的类都是Class类的对象(例如Class.new()
可创建一个类),所有的模块都是Module类的对象(例如Module.new()
可创建一个模块 。
1 2 3 4 5 6 7 class C;end module M;end C.class M.class C1 = Class .newc1 = C1 .new
所以,类或模块自身也是对象 ,为了与类实例化后得到的对象(例如Person.new
)进行区分,类或模块自身对象称为类对象或模块对象 (当然,它们是Class类、Module类的实例对象)。
既然类或模块自身也是对象,它们当然也有自己的实例变量和实例方法,它们称为类的实例变量和类的实例方法。实际上,类的实例方法,就是类方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module M @a = 3 puts "@a in M: #@a" def M .f puts "in M.f" end end M.f class C include M @b = 4 puts "@b in C: #@b" def C .g puts "in C.g" end end C.g
如果了解JavaScript,会发现Ruby的类方法、类的实例变量相当于JavaScript中直接在类对象上定义属性。模块的实例变量和模块方法同理。总而言之,类或模块的实例变量和它们自身的实例方法,都属于类对象自身或模块自身。
1 2 3 4 5 class C {}C.name = "junmajinlong"
因为类方法和模块方法的定义方式都是def obj.x
,这表示类方法和模块方法都是定义在类对象或模块对象的单例类空间中的 。
定义类方法或模块方法时,更常见的方式是在类上下文或模块上下文中使用self来代指类名或模块名。这有两种方式:def self.x
或class << self
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class C def self .f1 puts "class method: self.f1" end class << self def f2 puts "class method: f2" end def f3 puts "class method: f3" end end end C.f1 C.f2 C.f3 module M def self .f1 puts "module method: self.f1" end class << self def f2 puts "module method: f2" end end end M.f1 M.f2
一定要注意,不要在class << self
内部再使用def self.x
,因为前者已经进入了类对象或模块对象的单例类空间,再在其内使用def self.x
又打开了该单例类对象的单例空间,即打开了类对象或模块对象的单例类的单例类空间,并在其中定义了方法x。如果真的这样定义了,需使用singleton_class
方法返回类或模块的单例类,再调用它的单例方法。
1 2 3 4 5 6 7 8 9 10 11 12 class C class << self def f1 puts "singleton_class of C" end def self .f2 puts "singleton class of C's singleton_class" end end end C.f1 C.singleton_class.f2
既然类或模块自身也是对象,也可以为它们定义属性的getter和setter方法,比如使用attr_accessor
来定义。但是要注意,如果直接在class…end内部使用attr_xxx
来定义,它是为实例对象定义存取方法,而非为类对象定义。要为类对象定义属性的存取方法,需进入类对象或模块对象的类空间上下文,即它们的单例类空间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class C class << self attr_accessor :name end end module M class << self attr_accessor :name end end C.name = "junmajinlong" p C.name M.name = "gaoxiaofang" p M.name
子类会继承父类的类方法(即类的实例方法),但不会继承父类的类实例变量 。
类实例变量专属于类对象,显然是不共享的,如果想要共享变量,可使用类变量。
对于类方法,它是类对象的实例方法,它们定义在类对象的单例类空间中,在逻辑上它们是类对象所独有的。但从另一个角度上来看,子类本就是衍生自父类的,父类具有的行为方法,子类也应当具备,包括父类的类方法(其本质是:类方法定义在单例类空间中,子类继承父类时,子类的单例类空间同时也会继承父类的单例类空间,所以子类也能访问父类的类方法)。
1 2 3 4 5 6 7 8 9 10 11 12 class C @cls_name = "C" def self .f puts "in self.f" end end class < D end D.f
最后,Class是Module的子类,所以类也是一个模块 。Module类中定义了很多直接以模块(名)为接收者的方法,它们也都适用于类(名)。例如,Module中有一个方法instance_methods用来返回模块中所有已定义的实例方法,在类名上使用也是一样允许的:
1 2 3 4 5 6 class C def f1 ;end def f2 ;end end p C.instance_methods(false )
其实,很多时候的模块和类是『等价的』概念。例如,类空间的上下文和模块空间的上下文的性质是一样的,类中可以定义类变量、类方法、类实例变量、实例方法,模块中也可以定义类变量、模块方法、模块变量、实例方法(Mix-in后将作为类中对象的实例方法),再例如,类和模块之间均可以互相嵌套,等等,只不过模块不可直接被实例化、模块不被继承,而是被Mix-in ,但由于某对象Mix-in模块后,该模块会加入到该对象的祖先链中,因此该对象也可以看作是该模块的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class C @name = "junmajinlong" def self .f end def f end end module M @name = "gaoxiaofang" def self .ff end def ff end end
子类会继承哪些东西 子类在继承父类时,将继承:
实例方法
类方法
类变量
方法的可见性规则
常量
常量也可被继承。但在子类中定义父类同名常量时,意味着对常量做修改,而常量在含义上是不能修改的。但事实上,子类中定义同名常量并没有修改常量,因为常量是通过名称空间来引用的,定义在不同名称空间中的常量是不同的常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Point def initialize (x, y ) @x = x @y = y end ORIGIN = Point .new(0 , 0 ) def to_s "(#@x, #@y)" end end class Point3D < Point def initialize (x, y, z ) super (x, y) @z = z end ORIGIN = Point3D .new(0 , 0 , 0 ) def to_s "(#@x, #@y, #@z)" end end puts Point : :ORIGIN puts Point3 D::ORIGIN