1 發送一條消息給一個對象
當你調用一個方法時,你也就是發送了一條消息給一個對象,在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将來的版本,可能會實作這個。
在一些庫的源碼中,我們能看到這種代碼:
在一個類的體内,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所繼承。
下面的方法可能是最好的方法,我們沒有使用類變量,而是使用類執行個體變量:
這裡我們打開了一個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的性能不好,因為它儲存了太多的狀态和上下文...