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
在ruby中任何一個類都可以隨時打開的,這樣可以寫出像2.days_ago這樣優美 的code,我們打開Array,并定義一個sort_by方法: Ruby代碼 class Array def sort_by(sysm) self.sort{|x,y| x.send(sym) <=> y.send(sym)} end end 我們看看運行結果: Ruby代碼 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外還有一個地方應該注意attr_reader,attr_reader相當于定義了name, age,heigh三個方法,而Array里的sort方法只需要提供一個比較方法: x.send(sym) <=> y.send(sym) 通過send得到person的屬性值,然后在使用<=>比較 二、定制一個object << object ruby不僅可以打開一個類,而且可以打開一個對象,給這個對象添加或定制功能,而不影響 其他對象: Ruby代碼 a = "hello" b = "goodbye" def b.upcase gsub(/(.)(.)/)($1.upcase + $2) end puts a.upcase #HELLO puts b.upcase #GoOdBye 我們發現b.upcase方法被定制成我們自己的了 如果想給一個對象添加或定制多個功能,我們不想多個def b.method1 def b.method2這么做 我們可以有更模塊化的方式: Ruby代碼 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 這個class被叫做singleton class,因為這個class是針對b這個對象的。 和設計模式singleton object類似,只會發生一次的東東我們叫singleton. << self 給你定義的class添加行為 Ruby代碼 class TheClass class << self def hello puts "hello!" end end end TheClass.hello #hello! <<self修改了你定義class的class,這是個很有用的技術,他可以定義class級別 的helper方法,然后在這個class的其他的定義中使用。下面一個列子定義了訪問 函數,我們希望訪問的時候把成員數據都轉化成string,我們可以通過這個技術來 定義一個Class-Level的方法accessor_string: Ruby代碼 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 # Fri Nov 21 09:50:51 +0800 2008 通過extend module給你的對象添加行為,module里面的方法變成了對象里面的 實例方法: Ruby代碼 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 三、創建一個可參數化的類: 如果我們要創建很多類,這些類只有類成員的初始值不同,我們很容易想起: Ruby代碼 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 這種方式是錯誤的,實際上Ruby中的類成員不僅在這個類中被所有對象共享, 實際上會被整個繼承體系共享,所以我們調用Terran.home_planet,會輸出 “Mars”,而我們期望的是Earth 一個可行的方法: 我們可以通過class_eval在運行時延遲求值來達到目標: Ruby代碼 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 最好的方法: 我們不使用類變量,而是使用類實例變量: Ruby代碼 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 四、Ruby中的Continuations: Continuations恐怕是Ruby中最難理解的概念了,它可以處理非局部的跳轉, 它保存了返回地址和執行的環境,和c中的setjmp和longjump類似,但它保存 了更多的信息: axgle舉的曹操的例子很形象,我們拿過來看看: 來自[http://www.javaeye.com/topic/44271] 曹操(caocao)被譽為“古代輕功最好的人 ”,是因為“說曹操,曹操到”這句名言。 在ruby中,曹操的這種輕功被稱為callcc. Ruby代碼 callcc{|caocao| for say in ["曹操","諸葛亮","周瑜"] caocao.call if say=="曹操" puts say #不會輸出,因為曹操已經飛出去了 end }#“曹操”飛到這里來了(跳出了callcc的block,緊跟在這個block后面,繼續執行下面的ruby代碼) puts "到" callcc里的caocao是個"延續"(Continuation)對象.這個對象只有名叫“call"的這樣一個方法。 當執行了caocao.call后,caocao就會飛到callcc的塊(block)后面,讓ruby繼續執行其下面的代碼。 我上面給出的是一個從塊里頭”飛“到塊外面的例子;下面是Programming Ruby給出的從代碼后面”飛“到代碼前面的例子: Ruby代碼 arr = [ "Freddie", "Herbie", "Ron", "Max", "Ringo" ] callcc{|$cc|}#下面的$cc.call如果被執行,就會飛回到這里(callcc的塊之后)。 puts(message = arr.shift) $cc.call unless message =~ /Max/ 例子大多來自<<The ruby way>>