1 发送一条消息给一个对象
当你调用一个方法时,你也就是发送了一条消息给一个对象,在ruby中我们能够在运行时决定那个方法被调用。send 方法就是做这个的,他接受一个symbol为参数.
举个简单的例子,假设我们要写一个排序,我们想要使用不同的域作为比较的key。虽然我们这时可以用block,可是如果使用send的话,我们能有一个更优美的写法:
__send__ 其实也就是send方法的别名了。不过这边建议用__send__,这是因为send有可能作为一个用户自己定义的方法。
在1.9中,send方法不能调用private方法了,不过我们能够使用__send!来调用:
2 特殊化一个单独的对象
在很多oo语言里,一个类的所有的对象共享相同的行为,类作为一个模板,当构造器调用时,制造一个拥有相同接口的对象。
ruby中我们可以改变一个对象运行时的状态。你可以给对象一个私有的,匿名的子类:所有的原始的方法都是可用的。由于联系到这个对象上的行为是私有的,因此它只发生一次。一件只发生一次的事叫做“singleton”,比如singleton 方法,和singleton类.
看下面的代码:
加一个singleton 方法到一个对象,也就是创建了一个singleton 的类,然后这个类的父类是这个对象的类,如果你加多个方法到一个对象,这是你可以直接实现一个singleton 类:
这里要注意的是,一些更“primitive”的对象(比如Fixnum),不能加singleton 方法,这是因为这些对象,不是存引用在内存中的。但是在ruby将来的版本,可能会实现这个。
在一些库的源码中,我们能看到这种代码:
在一个类的体内,self 就指这个类自己,在这个类中的实例方法,其实也就是外面类的类方法:
使用这个技术的另一个原因是,可以创建一个类级别的帮助方法,然后我们就能在这个类的其他地方使用它了.
extend 方法能够mix一个模块到一个对象:
3 创建一个带参数的类
假设我们想要创建一个多样的类,也就是说,可以通过控制类变量来控制它的多种状态:
这样是可以的,这时如果我想要定义一些与Terran类似的类,你可能会马上想到是可以给这些类抽象出来一个超类就行了:
(注意,这里是错误的方法)
当你调用Terran.home_planet时,在1.9中会打印出nil,在1.8中会打印出Mars.
为什么会这样?答案是class variables 不是真正的class variables 。他们不属于类,而是属于整个继承体系。class variables不能从父类所被复制,但是能够从父类所被共享。
我们可以剔除掉类变量在基类中的定义,可是这时我们定义的类方法就不能工作了。
这里有一个稍好一些的方法,使用了class_eval 方法:
这个可以打印出我们想要的结果任何IntelligentLife里定义的实例变量,或者实例方法,都会被Terran 和 Martian所继承。
下面的方法可能是最好的方法,我们没有使用类变量,而是使用类实例变量:
这里我们打开了一个singleton class,定义了一个存取方法home_planet,两个子类调用他们自己的accessors 来设置变量.我们其实还可以给home_planet=方法设置为 private的。
这里其实还可以这样做:
4 使用Continuations 来实现一个生成器
ruby的一个更抽象的特性就是continuation。这是一种控制非局部的跳转和返回的方法。一个continuation 对象存储着一个返回的地址,和一个上下文.他看起来很像c中的setjmp/longjmp ,可是他存储着更多的上下文.
Kernel 的方法callcc接受一个block,返回一个Continuation 对象。这个被返回的对象作为一个参数被传递进这个block.Continuation唯一的方法就是call,调用它将会引起一个非局部的返回,执行callc的block直到它的尾部。
其实Continuation很像游戏中的保存游戏的特性。最好的理解Continuation的方法,就是去看电影Run, Lola, Run (哈哈,就是罗拉快跑).
Continuation的最好的例子就是生成器,现在生成器已经是ruby的一部分了。
请看下面的使用生成器的Fibonacci numbers 的例子:
这里要注意的是,continuations的性能不好,因为它保存了太多的状态和上下文...
当你调用一个方法时,你也就是发送了一条消息给一个对象,在ruby中我们能够在运行时决定那个方法被调用。send 方法就是做这个的,他接受一个symbol为参数.
举个简单的例子,假设我们要写一个排序,我们想要使用不同的域作为比较的key。虽然我们这时可以用block,可是如果使用send的话,我们能有一个更优美的写法:
class Person
attr_reader :name, :age, :height
def initialize(name, age, height)
@name, @age, @height = name, age, height
end
def inspect
"#@name #@age #@height"
end
end
class Array
def sort_by(sym) # Our own version of sort_by
self.sort {|x,y| x.send(sym) <=> y.send(sym) }
end
end
people = []
people << Person.new("Hansel", 35, 69)
people << Person.new("Gretel", 32, 64)
people << Person.new("Ted", 36, 68)
people << Person.new("Alice", 33, 63)
p1 = people.sort_by(:name)
p2 = people.sort_by(:age)
p3 = people.sort_by(:height)
p p1 # [Alice 33 63, Gretel 32 64, Hansel 35 69, Ted 36 68]
p p2 # [Gretel 32 64, Alice 33 63, Hansel 35 69, Ted 36 68]
p p3 # [Alice 33 63, Gretel 32 64, Ted 36 68, Hansel 35 69]
__send__ 其实也就是send方法的别名了。不过这边建议用__send__,这是因为send有可能作为一个用户自己定义的方法。
在1.9中,send方法不能调用private方法了,不过我们能够使用__send!来调用:
class Foo
private
def foo
"aa"
end
end
p Foo.new.__send!(:foo) # => nil
p Foo.new.send(:foo) #private method `foo' called for #<Foo:0xa89530> (NoMethodError)
2 特殊化一个单独的对象
在很多oo语言里,一个类的所有的对象共享相同的行为,类作为一个模板,当构造器调用时,制造一个拥有相同接口的对象。
ruby中我们可以改变一个对象运行时的状态。你可以给对象一个私有的,匿名的子类:所有的原始的方法都是可用的。由于联系到这个对象上的行为是私有的,因此它只发生一次。一件只发生一次的事叫做“singleton”,比如singleton 方法,和singleton类.
看下面的代码:
a = "hello"
b = "goodbye"
def b.upcase # create single method
gsub(/(.)(.)/) { $1.upcase + $2 }
end
puts a.upcase # HELLO
puts b.upcase # GoOdBye
加一个singleton 方法到一个对象,也就是创建了一个singleton 的类,然后这个类的父类是这个对象的类,如果你加多个方法到一个对象,这是你可以直接实现一个singleton 类:
b = "goodbye"
class << b
def upcase # create single method
gsub(/(.)(.)/) { $1.upcase + $2 }
end
def upcase!
gsub!(/(.)(.)/) { $1.upcase + $2 }
end
end
puts b.upcase # GoOdBye
puts b # goodbye
b.upcase!
puts b # GoOdBye
这里要注意的是,一些更“primitive”的对象(比如Fixnum),不能加singleton 方法,这是因为这些对象,不是存引用在内存中的。但是在ruby将来的版本,可能会实现这个。
在一些库的源码中,我们能看到这种代码:
class SomeClass
# Stuff...
class << self
# more stuff...
end
# ... and so on.
end
在一个类的体内,self 就指这个类自己,在这个类中的实例方法,其实也就是外面类的类方法:
class TheClass
class << self
def hello
puts "hi"
end
end
end
# invoke a class method
TheClass.hello # hi
使用这个技术的另一个原因是,可以创建一个类级别的帮助方法,然后我们就能在这个类的其他地方使用它了.
class MyClass
class << self
def accessor_string(*names)
names.each do |name|
class_eval <<-EOF
def #{name}
@#{name}.to_s
end
EOF
end
end
end
def initialize
@a = [ 1, 2, 3 ]
@b = Time.now
end
accessor_string :a, :b
end
o = MyClass.new
puts o.a # 123
puts o.b # Mon Apr 30 23:12:15 CDT 2001
extend 方法能够mix一个模块到一个对象:
module Quantifier
def any?
self.each { |x| return true if yield x }
false
end
def all?
self.each { |x| return false if not yield x }
true
end
end
list = [1, 2, 3, 4, 5]
list.extend(Quantifier)
flag1 = list.any? {|x| x > 5 } # false
flag2 = list.any? {|x| x >= 5 } # true
flag3 = list.all? {|x| x <= 10 } # true
flag4 = list.all? {|x| x % 2 == 0 } # false
3 创建一个带参数的类
假设我们想要创建一个多样的类,也就是说,可以通过控制类变量来控制它的多种状态:
class Terran
@@home_planet = "Earth"
def Terran.home_planet
@@home_planet
end
def Terran.home_planet=(x)
@@home_planet = x
end
#...
end
这样是可以的,这时如果我想要定义一些与Terran类似的类,你可能会马上想到是可以给这些类抽象出来一个超类就行了:
(注意,这里是错误的方法)
class IntelligentLife # Wrong way to do this!
@@home_planet = nil
def IntelligentLife.home_planet
@@home_planet
end
def IntelligentLife.home_planet=(x)
@@home_planet = x
end
#...
end
class Terran < IntelligentLife
@@home_planet = "Earth"
#...
end
class Martian < IntelligentLife
@@home_planet = "Mars"
#...
end
当你调用Terran.home_planet时,在1.9中会打印出nil,在1.8中会打印出Mars.
为什么会这样?答案是class variables 不是真正的class variables 。他们不属于类,而是属于整个继承体系。class variables不能从父类所被复制,但是能够从父类所被共享。
我们可以剔除掉类变量在基类中的定义,可是这时我们定义的类方法就不能工作了。
这里有一个稍好一些的方法,使用了class_eval 方法:
class IntelligentLife
def IntelligentLife.home_planet
class_eval("@@home_planet")
end
def IntelligentLife.home_planet=(x)
class_eval("@@home_planet = #{x}")
end
#...
end
class Terran < IntelligentLife
@@home_planet = "Earth"
#...
end
class Martian < IntelligentLife
@@home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
这个可以打印出我们想要的结果任何IntelligentLife里定义的实例变量,或者实例方法,都会被Terran 和 Martian所继承。
下面的方法可能是最好的方法,我们没有使用类变量,而是使用类实例变量:
class IntelligentLife
class << self
attr_accessor :home_planet
end
#...
end
class Terran < IntelligentLife
self.home_planet = "Earth"
#...
end
class Martian < IntelligentLife
self.home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
这里我们打开了一个singleton class,定义了一个存取方法home_planet,两个子类调用他们自己的accessors 来设置变量.我们其实还可以给home_planet=方法设置为 private的。
这里其实还可以这样做:
module IntelligentLife
attr_accessor :home_planet
end
class Terran
class << self
include IntelligentLife
end
self.home_planet = "Earth"
#...
end
class Martian
class << self
include IntelligentLife
end
self.home_planet = "Mars"
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
4 使用Continuations 来实现一个生成器
ruby的一个更抽象的特性就是continuation。这是一种控制非局部的跳转和返回的方法。一个continuation 对象存储着一个返回的地址,和一个上下文.他看起来很像c中的setjmp/longjmp ,可是他存储着更多的上下文.
Kernel 的方法callcc接受一个block,返回一个Continuation 对象。这个被返回的对象作为一个参数被传递进这个block.Continuation唯一的方法就是call,调用它将会引起一个非局部的返回,执行callc的block直到它的尾部。
其实Continuation很像游戏中的保存游戏的特性。最好的理解Continuation的方法,就是去看电影Run, Lola, Run (哈哈,就是罗拉快跑).
Continuation的最好的例子就是生成器,现在生成器已经是ruby的一部分了。
请看下面的使用生成器的Fibonacci numbers 的例子:
class Generator
def initialize
do_generation
end
def next
callcc do |here|
@main_context = here;
@generator_context.call
end
end
private
def do_generation
callcc do |context|
@generator_context = context;
return
end
generating_loop
end
def generate(value)
callcc do |context|
@generator_context = context;
@main_context.call(value)
end
end
end
# Subclass this and define a generating_loop
class FibGenerator < Generator
def generating_loop
generate(1)
a, b = 1, 1
loop do
generate(b)
a, b = b, a+b
end
end
end
# Now instantiate the class...
fib = FibGenerator.new
puts fib.next # 1
puts fib.next # 1
puts fib.next # 2
puts fib.next # 3
puts fib.next # 5
puts fib.next # 8
puts fib.next # 13
这里要注意的是,continuations的性能不好,因为它保存了太多的状态和上下文...