第十三章 Ruby 动态特性
一、单例类的位置和角色
1. 单例方法在何处定义
对象的单例方法定义在对象单例类中。每个对象实际上有两个类:
■ 多个对象实例共享的类
■ 单例类
可以将某个对象的单例类看作是它独有的方法保护区,仅属于该对象,其它对象不能分享
obj = Object.new
def obj.talk
end
obj.talk
2. 直接检查和修改单例类
单例类是匿名的,可以使用 class 关键字的一个特殊形式来完成,class 后面跟一个常量
class C
end
使用以下面特殊记法来打开单例类的定义体
str = "I am a string"
class << str
end
puts str.twice
或者可以这样定义
def str.twice
end
使用 class << 定义类方法
class Ticket
4. 方法查找路径上的单例类
(1)方法查找的顺序(包含单例类)
(2)可以在单例类中混含模块
class C
end
module M
end
obj = C.new
class << obj
end
obj.talk
第二种方法是使用 extend 进行扩展
obj = C.new
obj.extend(M)
obj.talk
(3)在单例类中混含模块与在对象所属类中混含模块
class C
end
module M
end
c = C.new
c.talk # 输出:Hi from original class!
class << c
end
c.talk # 输出:Hello from module!
通过使用 ancestors 方法,可以清楚地看到任意类或模块的继承/混含层级结构
c = C.new
class << c
end
输出结果为
[M, C, Object, Kernel]
5. 更深入地学习类方法
class C
end
def C.a_class_method
end
class D < C
end
D.a_class_method # 输出:Singleton method defined on C
二、 方法族
1. 执行字符串
>> ("2+2")
=> 4
编写一个可以让别人在运行时键入其方法名的方法
print "Method name: "
m = gets.chomp
("def #{m}; puts 'Hi!'; end")
(m)
的危险性:从来没有用过、将来也不打算使用 的有经验的、专家级的 Ruby 程序员并不少见。
2. instance_:该方法将 self 变为 instance_ 调用的接收者
p self # 输出:main
a = []
a.instance_ { p self } # 输出:[]
instance_ 常用于访问其它对象的私有数据——特别是实例变量
class C
end
c = C.new
c.instance_ { puts @x }
3. 最有用的 族方法:class_
(1)class_ (即module_)可以进入类定义体中
C = Class.new
C.class_ do
end
c = C.new
c.some_method
(2)将外围作用的局部变量提供给 class_ 代码块
>> var = "initialized variable"
=> "initialized variable"
>> class C
>> puts var
>> end
NameError: undefined local variable or method `var' for C:Class
>> C.class_ { puts var }
initialized variable
(3)如果想把外部作用域的变量硬塞到实例方法中,可以使用另外一种技术来生成方法
>> C.class_ { define_method("talk") { puts var }
=> #<Proc:0x003452f4@(irb):8>
三、可调对象
1. Proc 对象
(1)用一个代码块来实例化 Proc 类,可以产生一个 Proc 对象
pr = Proc.new { puts "Inside a Proc's block" }
执行该代码块(Proc 对象)需要显式地调用
pr.call # 输出:Inside a Proc's block
2. 作为闭包的 Proc 对象
def call_some_proc(pr)
end
a = "'a' to be used in Proc block"
pr = Proc.new { puts a }
pr.call
call_some_proc(pr)
输出结果(Proc 对象随身携带了它的上下文,称为闭包)
'a' to be used in Proc block
irrelevant 'a' in method scope
'a' to be used in Proc block
3. Proc 对象的参数
(1)传递和接收参数
pr = Proc.new {|x| puts "Called with argument #{x}" }
pr.call(100) # 输出: Called with argument 100
(2)多个参数的调用
>> pr = Proc.new {|x,y,z| p x,y,z }
=> #<Proc:0x001b5598@(irb):1>
>> pr.call(1,2)
1
2
nil
>> pr.call(1,2,3,4)
1
2
3
(3)使用星号操作符(*)将所有的参数合并为一个参数(参数变量 x 被设置为一个数组)
pr = Proc.new {|*x| p x }
pr.call
pr.call(1)
pr.call(1,2)
这段代码的输出
[]
[1]
[1, 2]
让 Proc 对象接受多个参数,而且最后一个参数具有合并多个参数的能力
pr = Proc.new {|x,*y| p x, y }
pr.call(1,2,3)
输出为
1
[2, 3]
4. 用 lambda 关键字生成匿名函数
(1)lambda 关键字可以用来生成匿名函数,然后可以给它发送“call”消息执行函数
>> lam = lambda { puts "A lambda!" }
=> #<Proc:0x00330cb4@(irb):31>
>> lam.call
A lambda!
正如我们所看到的,lambda 不是 Lambda 类的对象,它们是 Proc 类的对象
>> lam.class
=> Proc
和所有的 Proc 对象一样,lambda 都是闭包。
(2)lambda 和 Proc 对象的差别与 return 关键字有关
def return_test
end
return_test
此例可见,lambda 中的 return 从 lambda 返回,而 Proc 中的 return 从外围方法返回
5. 再论代码块
可以在方法中将代码块转换成 Proc 对象,通过一个变量捕获代码块,该变量是方法参数列表中的一个参数变量,它的名字必须以 & 开头
def grab_block(&block)
end
grab_block { puts "This block will end up in the variable 'block'" }
&block 变量必须是参数列表中的最后一项
def grab_block(x,y,*z,&block)
可以反过来将 Proc 对象或 lambda 转换为一个代码块,使用 & 可以达到此目的
lam = lambda { puts "This lambda will serve as a code block" }
grab_block &lam
下面是另一种方式
grab_block &lambda { puts "This lambda will serve as a code block" }
6. 作为对象的方法
通过 method 方法,并以方法名作为参数,就可以得到一个方法对象
class C
end
c = C.new
meth = c.method(:talk)
调用它自己
meth.call # 输出:Method-grabbing test!
可以将方法和它所绑定的对象解绑定(unbind),然后再将它绑定到另一个对象上(bind)
class D < C
end
d = D.new
unbound = meth.unbind
unbound.bind(d).call
如果想直接得到解绑定的方法对象,而本想通过已绑定的方法使用 unbind 来得到方法对象,则可以使用 instance_method 方法
unbound = C.instance_method(:talk)
绑定和解绑定技术的使用范例
class A
end
class B < A
end
class C < B
end
c = C.new
c.a_method # 输出:Definition in class B (subclass of A)
默认情况下,实例会执行它在方法查找路径上找到的第一个匹配的方法;
有没有办法让最底层的类实例,在接收到消息“a_method”时执行该方法
A.instance_method(:a_method).bind(c).call # 输出:Definition in class A
可以将此行为放在类 C 的一个方法中
class C
end
四、回调方法和钩子方法
1. 用 method_missing 拦截不能识别的消息
method 是一张安全网:提供了拦截不可响应的消息并对其进行适当处理的办法
class C
end
C.new.anything
2. 用 Module#included 捕捉混含操作
module M
end
class C
end
当 M 被混含到 C 中时,消息“I have just been mixed into C.”
3. 用 Class#inherited 拦截继承
class C
end
class D < C
end
当 D 继承 C 时自动触发对 inherited 的调用,因此该代码产生下面的输出
C just got subclassed by D
4. 用 Module#const_missing 拦截引用不可识别的常量
class C
end
puts C::A
puts C::A
a is undefined—setting it to 1.
1
1
五、覆盖和增加核心功能
Ruby 最强大的特性之一是可以修改和增强语言核心功能。可以像打开你自己的类那样,很容易地打开和修改核心类。可以按照自己的意愿为它添加方法或覆盖旧的方法。
但是,当你在自己的程序中对 Ruby 核心做出改变时,必须非常非常小心,。如果编写提供给别人使用的代码库,而库中包含了改变核心类和对象的代码,那么对每个使用你的代码人来说,你修改了游戏规则。