定義模組


Ruby中,模組(Module)中可以定義變數與方法。透過適當的設計,你可以將物件之間可能共用的實作抽離至模組中,在必要時讓類別include,使得類別定義時得以精簡。

舉個例子來說,你會定義以下的球類別,並定義了一些比較大小的方法:
class Ball
attr_reader :radius

def initialize(radius)
@radius = radius
end

def <=>(that) self.radius - that.radius end

def <(that) (self <=> that) < 0 end
def <=(that) (self < that) || (self <=> that) == 0 end
def >(that) !(self <= that) end
def >=(that) !(this < that) end
def ==(that) (self <=> that) == 0 end

def eql?(that)
if self.equal?(that)
return true
end
if that.is_a?(Ball)
return self == that
end
return false
end

def hash
41 * @radius
end

def to_s
"Ball(#{@radius})"
end
end

事實上,比較大小順序這件事,許多物件都會用的到,仔細觀察以上的程式碼,你會發現可抽離的共用比較方法,你可以將之重新設計為模組:
# encoding: Big5
module Comparable
def <=>(that)
raise RuntimeError, "必須實作 <=> 方法"
end

def <(that) (self <=> that) < 0 end
def <=(that) (self < that) || (self <=> that) == 0 end
def >(that) !(self <= that) end
def >=(that) !(this < that) end
def ==(that) (self <=> that) == 0 end
end

模組中除了<=>沒有實作之外,其它的方法都實作了。現在有了Comparable模組,你可以在設計球類別時更為精簡,如果你需要彼此比較的功能,則只要將Comparable模組include進來並<=>方法即可以:
class Ball
include Comparable

attr_reader :radius

def initialize(radius)
@radius = radius
end

def <=>(that) self.radius - that.radius end

def eql?(that)
if self.equal?(that)
return true
end
if that.is_a?(Ball)
return self == that
end
return false
end

def hash
41 * @radius
end

def to_s
"Ball(#{@radius})"
end
end

將Comparable模組include至Ball類別,如此模組中定義的方法,就會成為Ball的實例方法,在Ruby中稱這樣的機制為Mix-in

事實上,Ruby確實內建了 Comparable 模組來作比大小這種事,所以實際上你沒有撰寫上面的Comparable範例,你的Ball類別還是可以比大小,因為如此就是include內建的Comparable模組。

類似地,如果你在收集物件之後,必須迭代、取得最大物件、最小物件、排序、尋找物件等動作,不用親自撰寫,只要include內建的 Enumerable 模組就可以了。例如:
class Pond
include Enumerable

def initialize(list = [])
@list = list
end
def <<(obj)
@list << obj
end

def each
@list.each { |obj|
yield(obj)
}
end
end

pond = Pond.new([Ball.new(10), Ball.new(20)])
pond << Ball.new(5)
pond << Ball.new(15)
pond << Ball.new(10)

puts pond.include? Ball.new(5) # true
print "#{pond.sort}\n" # [Ball(5), Ball(10), Ball(10), Ball(15), Ball(20)]
puts "Max: #{pond.max}" # Max: Ball(20)
puts "Min: #{pond.min}" # Min: Ball(20)

pond.each_with_index do |ball, index|
puts "#{index} - #{ball}"
end

將Emunerable模組include至類別之後,唯一要實作的就是each方法,實作如何逐一迭代,其餘的方法,Emunerable皆已經使用each方法為基礎實作完成,所以你可以直接呼叫使用。