Ruby元编程读书笔记十二
Class Defintions Demystified
跟其他的面向对象的编程语言不同,在 Ruby 中,当使用 class 关键字时,并非是在指定对象未来的行为方式,相反,实际上是在运行普通的代码。事实上,可以在类定义中放入任何代码。而且要牢记,类只是一个增强的模块,因此我们在此学到的任何东西都可以应用于模块。当看到关于“类定义”内容的时候,也可以把它认为是“模块定义”。
class MyClass
puts 'Hello!'
end
#=> Hello!
跟方法和块一样,类定义也会返回最后一条语句的值。
The Current Class
不管处在 Ruby 程序的哪个位置,总存在一个 当前对象:self。类似的,也总是有一个 当前类 或模块存在。当定义一个方法时,该方法将成为当前类的一个实例方法。
尽管可以用 self 获得当前对象,但 Ruby 并没有相应的方式来获得当前类的引用。然而,跟踪当前类并不难,每当通过 class 关键字来打开一个类,这个类就成为当前类。
class 关键字有一个限制:它需要一个类的名字。但是在某些情况下,可能我们并不知道要打开的类的名字。设想一个以类为参数的方法,它给这个类添加了一个新的实例方法:
def add_method_to(a_class)
# TODO: 在 a_class 上定义方法 m()
end
这时,我们需要一种新的方式,它 不需要使用 class 关键字就能修改当前类,它就是 class_eval()。
- Module#class_eval() 方法(或者它的别名 module_eval())会在一个已存在类的上下文中执行一个块:
def add_method_to(a_class) a_class.class_eval do def m; 'Hello!'; end end end add_method_to String "abc".m #=> "Hello!" -
实际上 Module#class_eval() 方法和 Object#instance_eval() 方法截然不同。instance_eval() 方法仅仅会修改 self,而 class_eval() 方法会同时修改 self 和当前类。通过修改当前类,class_eval() 实际上是重新打开了该类,就像 class 关键字所做的一样。
- Module#class_eval() 实际上比 class 关键字更加灵活,它可以对任何代表类的变量使用 class_eval() 方法,而 class 只能使用常量。另外,class 会打开一个新的作用域,这样将丧失当前绑定的可见性,而 class_eval() 方法则使用 扁平作用域,这意味着可以引用 class_eval() 块外部作用域中的变量。
当前类及其特殊情况
Ruby 解释器总是会追踪当前类:
class MyClass
def method_one
def method_two; "Hello!";end
end
end
obj = MyClass.new
obj.method_one
obj.method_two #=> "Hello!"
在这里 method_two() 方法属于哪个类?或者说,在定义 method_two() 方法时,谁是当前对象?这种情况下,当前类不可能时 self,因为 self 根本不是类,实际上,这时当前类的角色由 self 的类来充当—-MyClass。同样的原则还可以应用于处于顶级作用域的时候。这时,当前类是 Object—-main 对象的类,这也就是为什么当在顶级作用域中定义方法的时候,这个方法会成为 Object 类实例方法的原因。
Current Class Wrap-up
- 在类定义中,当前对象 self 就是正在定义的类。
- Ruby 解释器总是追踪当前类(或模块)的引用,所有使用 def 定义的方法都成为当前类的实例方法。
- 只要有一个类的引用,则可以用 class_eval()(或 module_eval())方法打开这个类。
Class Instance Variables
Ruby 解释器假定所有的实例变量都属于当前对象 self,当定义类时也是如此:
class MyClass
@my_var = 1
end
在类定义的时候, self 的角色由类本身担任,因此实例变量 @my_var 属于这个类,注意 类的实例变量不同于类的对象的实例变量。
class MyClass
@my_var = 1
def self.read; @my_var; end
def write; @my_var = 2; end
def read; @my_var; end
end
obj = MyClass.new
obj.write
obj.read #=> 2
MyClass.read #=> 1
上面的代码定义了两个实例变量,它们正好都叫 @my_var,但是它们分属于不同的作用域,并属于不同的对象。要牢记 类也是对象,而且需要自己在程序中追踪 self。其中一个 @my_var 变量定义于 obj 充当 self 的时刻,因此它是 obj 对象的实例变量。另外一个 @my_var 变量定义于 MyClass 充当 self 的时刻,因此它是 MyClass 的实例变量—-也就是 类实例变量。
类实例变量是属于 Class 类对象的普通实例变量,类实例变量仅仅可以被类本身所访问,而不能被类的实例或子类所访问。