Ruby for Rails 最佳实践十三

第十三章 Ruby 动态特性

一、单例类的位置和角色

1. 单例方法在何处定义

对象的单例方法定义在对象单例类中。每个对象实际上有两个类:

■ 多个对象实例共享的类

■ 单例类

可以将某个对象的单例类看作是它独有的方法保护区,仅属于该对象,其它对象不能分享

obj = Object.new

def obj.talk

         puts "Hi!"

end

obj.talk

 

2. 直接检查和修改单例类

单例类是匿名的,可以使用 class 关键字的一个特殊形式来完成,class 后面跟一个常量

class C

         # method and constant definitions here

end

 

使用以下面特殊记法来打开单例类的定义体

str = "I am a string"

class << str

         def twice

                   self + " " + self

         end

end

puts str.twice

 

或者可以这样定义

def str.twice

         self + " " + self

end

 

使用 class << 定义类方法

class Ticket

         class << self

                   def most_expensive(tickets)

                   # etc.

 

4. 方法查找路径上的单例类

(1)方法查找的顺序(包含单例类)

 

 

 

(2)可以在单例类中混含模块

class C

end

 

module M

         def talk

                   puts "Hello."

         end

end

 

obj = C.new

class << obj

         include M

end

obj.talk

 

第二种方法是使用 extend 进行扩展

obj = C.new

obj.extend(M)

obj.talk

 

(3)在单例类中混含模块与在对象所属类中混含模块

class C

         def talk

                   puts "Hi from original class!"

         end

end

 

module M

         def talk

                   puts "Hello from module!"

         end

end

 

c = C.new

c.talk # 输出:Hi from original class!

class << c

         include M

end

c.talk # 输出:Hello from module!

 

通过使用 ancestors 方法,可以清楚地看到任意类或模块的继承/混含层级结构

c = C.new

class << c

         include M

         p ancestors

end

输出结果为

[M, C, Object, Kernel]

 

5. 更深入地学习类方法

class C

end

 

def C.a_class_method

         puts "Singleton method defined on C"

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

  def initialize

    @x = 1

  end

end

c = C.new

c.instance_ { puts @x }

 

3. 最有用的 族方法:class_

(1)class_ (即module_)可以进入类定义体中

C = Class.new

C.class_ do

  def some_method

    puts "Created in class_"

  end

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

        from (irb):3

>> 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)

  a = "irrelevant 'a' in method scope"

  puts a

  pr.call

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

  l = lambda { return }

  l.call

  puts "Still here!"

  p = Proc.new { return }

  p.call # 跳出方法体

  puts "You won't see this message!"

end

return_test

此例可见,lambda 中的 return 从 lambda 返回,而 Proc 中的 return 从外围方法返回

 

5. 再论代码块

可以在方法中将代码块转换成 Proc 对象,通过一个变量捕获代码块,该变量是方法参数列表中的一个参数变量,它的名字必须以 & 开头

def grab_block(&block)

  block.call

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

  def talk

    puts "Method-grabbing test!  self is #{self}."

  end

end

c = C.new

meth = c.method(:talk)

调用它自己

meth.call # 输出:Method-grabbing test!  self is #<C:0x353854>

 

可以将方法和它所绑定的对象解绑定(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

  def a_method

    puts "Definition in class A"

  end

end

class B < A

  def a_method

    puts "Definition in class B (subclass of A)"

  end

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

  def call_original

    A.instance_method(:a_method).bind(self).call

  end

end

 

四、回调方法和钩子方法

1. 用 method_missing 拦截不能识别的消息

method 是一张安全网:提供了拦截不可响应的消息并对其进行适当处理的办法

class C

  def method_missing(m)

    puts "There's no method called #{m} here -- please try again."

  end

end

C.new.anything

 

2. 用 Module#included 捕捉混含操作

module M

  def self.included(c)

    puts "I have just been mixed into #{c}."

  end

end

 

class C

  include M

end

当 M 被混含到 C 中时,消息“I have just been mixed into C.”

 

3. 用 Class#inherited 拦截继承

class C

  def self.inherited(subclass)

    puts "#{self} just got subclassed by #{subclass}"

  end

end

class D < C

end

当 D 继承 C 时自动触发对 inherited 的调用,因此该代码产生下面的输出

C just got subclassed by D

 

4. 用 Module#const_missing 拦截引用不可识别的常量

class C

  def self.const_missing(const)

    puts "#{const} is undefined—setting it to 1."

    const_set(const,1) # 如果常量不存在,则将它设置为系统常量

  end

end

puts C::A

puts C::A

 

a is undefined—setting it to 1.

1

1

 

五、覆盖和增加核心功能

Ruby 最强大的特性之一是可以修改和增强语言核心功能。可以像打开你自己的类那样,很容易地打开和修改核心类。可以按照自己的意愿为它添加方法或覆盖旧的方法。

 

但是,当你在自己的程序中对 Ruby 核心做出改变时,必须非常非常小心,。如果编写提供给别人使用的代码库,而库中包含了改变核心类和对象的代码,那么对每个使用你的代码人来说,你修改了游戏规则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值