在Ruby中要定義類別非常的簡單,例如你可以定義一個帳戶(Account)類別:
# encoding: Big5
class Account; end
acct = Account.new
def acct.initialize(number, name, balance)
@number = number
@name = name
@balance = balance
end
def acct.number
@number
end
def acct.name
@name
end
def acct.balance
@balance
end
def acct.deposit(money)
if money <= 0
raise ArgumentError, "必須是正數"
end
@balance += money
end
def acct.withdraw(money)
if money > @balance
raise RuntimeError, "餘額不足"
end
@balance -= money
end
acct.initialize("123-456-789", "Justin", 0)
puts acct.number # 123-456-789
puts acct.name # Justin
acct.deposit(100)
puts acct.balance # 100
acct.withdraw(50)
puts acct.balance # 50
上例中,class用來定義一個類別,為了突顯Ruby可直接於類別實例上定義方法的特性,現在還沒有定義類別內容。要建立類別的實例(Instance),直接呼叫Account.new來建立。建立物件之後,可以直接於物件建立單例方法(Singleton method),透過 . 來傳送訊息給接收者,執行對應的方法。
以@開頭的變數稱為實例變數(Instance variable),顧名思義,為個別物件擁有的變數,實例變數無法直接由外部存取,必須自行定義方法,外部才可以取得實例變數的值,這也就是為何,上例中要再特別定義name、number、balance等方法的原因。
上面這個例子定義了類別,但沒有封裝的概念,deposit、withdraw等方法,都僅屬於acct參考的物件擁有,可以將物件建立後的初始化動作,以及會用到的相關操作定義在類別之中。來看看下面這個例子:
# encoding: Big5
class Account
def initialize(number, name, balance)
@number = number
@name = name
@balance = balance
end
def number
@number
end
def name
@name
end
def name=(value)
@name = value
end
def balance
@balance
end
def deposit(money)
if money <= 0
raise ArgumentError, "必須是正數"
end
@balance += money
end
def withdraw(money)
if money > @balance
raise RuntimeError, "餘額不足"
end
@balance -= money
end
end
acct = Account.new("123-456-789", "Justin", 0)
puts acct.number # 123-456-789
puts acct.name # Justin
acct.deposit(100)
puts acct.balance # 100
acct.withdraw(50)
puts acct.balance # 50
acct.name = "Caterpillar"
puts acct.name # caterpillar
呼叫Account.new建立實例之後,會自動呼叫initialize方法進行初始化的動作,傳給Account.new的引數,會依序傳給initialize方法作為引數(如果要定義物件被銷毀前要執行的方法,可以搜尋ObjectSpace.define_finalizer方法的使用)。如果要使用等號指定,可以自行定義如name=的方法,將實例方法(Instance method)與實例變數定義在類別之中,每個建構的實例就都會擁有類別中定義的方法與各自的實例變數。
上例中,建立物件並初始化之後,為了可以取得與設定實例變數,必須自行定義方法。實際上,可以使用attr_reader方法定義取值方法,attr_writer定義設值方法,或者使用attr_accessor同時定義取值與設值方法,attr_reader、attr_writer與attr_accessor接受的是 符號型態 實例。例如:
# encoding: Big5
class Account
attr_reader :number, :balance
attr_accessor :name
def initialize(number, name, balance)
@number = number
@name = name
@balance = balance
end
def deposit(money)
if money <= 0
raise ArgumentError, "必須是正數"
end
@balance += money
end
def withdraw(money)
if money > @balance
raise RuntimeError, "餘額不足"
end
@balance -= money
end
end
acct = Account.new("123-456-789", "Justin", 0)
puts acct.number # 123-456-789
puts acct.name # Justin
acct.deposit(100)
puts acct.balance # 100
acct.withdraw(50)
puts acct.balance # 50
acct.name = "Caterpillar"
puts acct.name
在Ruby中定義類別之後,可以在後續再度開啟類別增加新的定義,所有已建構的實例,將直接擁有增加的新定義。例如 陣列型態 中就看過這麼一個例子:
>> class Array
>> def ^(that)
>> self + that - (self & that)
>> end
>> end
=> nil
>> admins = ["Justin", "caterpillar"]
=> ["Justin", "caterpillar"]
>> users = ["momor", "hamini", "Justin"]
=> ["momor", "hamini", "Justin"]
>> admins ^ users
=> ["caterpillar", "momor", "hamini"]
>>
>> def ^(that)
>> self + that - (self & that)
>> end
>> end
=> nil
>> admins = ["Justin", "caterpillar"]
=> ["Justin", "caterpillar"]
>> users = ["momor", "hamini", "Justin"]
=> ["momor", "hamini", "Justin"]
>> admins ^ users
=> ["caterpillar", "momor", "hamini"]
>>
Array原本沒有^方法,在上例中開啟Array類別定義了^方法,使Array實例都可以呼叫^方法。可以開啟類別定義新方法,既有的類別已定義的方法,也可以移除,只要使用remove_method方法(若必要,也可以移除實例變數,可搜尋remove_instance_variable方法的使用)。例如:
>> class Some
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s = Some.new
=> #<Some:0x28e7560>
>> s.some
some
=> nil
>> class Some
>> remove_method :some
>> end
=> Some
>> s.some
NoMethodError: undefined method `some' for #<Some:0x28e7560>
from (irb):60
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> s2 = Some.new
=> #<Some:0x28bbe08>
>> s2.some
NoMethodError: undefined method `some' for #<Some:0x28bbe08>
from (irb):62
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
>> def some
>> puts "some"
>> end
>> end
=> nil
>> s = Some.new
=> #<Some:0x28e7560>
>> s.some
some
=> nil
>> class Some
>> remove_method :some
>> end
=> Some
>> s.some
NoMethodError: undefined method `some' for #<Some:0x28e7560>
from (irb):60
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> s2 = Some.new
=> #<Some:0x28bbe08>
>> s2.some
NoMethodError: undefined method `some' for #<Some:0x28bbe08>
from (irb):62
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
在Ruby,定義於類別中的方法,預設會是公開(public),可以使用public、private或protected方法定義指定的方法為公開、私有或受保護,公開的方法可以直接指定物件接收者(包括self)直接呼叫,私有方法則只能在有繼承關係的類別體系中不透過self呼叫,受保護方法則可以在有繼承關係的類別體系中指定物件接收者(包括self)呼叫。例如:
>> class Some
>> def some
>> puts "some"
>> priv_method
>> end
>> def priv_method
>> puts "priv_method"
>> end
>> private :priv_method
>> end
=> Some
>> s = Some.new
=> #<Some:0x287fd78>
>> s.some
some
priv_method
=> nil
>> s.priv_method
NoMethodError: private method `priv_method' called for #<Some:0x287fd78>
from (irb):28
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
>> def some
>> puts "some"
>> priv_method
>> end
>> def priv_method
>> puts "priv_method"
>> end
>> private :priv_method
>> end
=> Some
>> s = Some.new
=> #<Some:0x287fd78>
>> s.some
some
priv_method
=> nil
>> s.priv_method
NoMethodError: private method `priv_method' called for #<Some:0x287fd78>
from (irb):28
from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>
initialize方法一定是private。
可以使用instance_of?方法測試物件是否為某個類別的實例,如果要知道是否為某個繼承體系的實例,則可以使用is_a?或===方法。例如:
>> s.instance_of? Some
=> true
>> s.instance_of? Object
=> false
>> s.is_a? Object
=> true
>> s.is_a? Some
=> true
>> Some === s
=> true
>> Object === s
=> true
>>
=> true
>> s.instance_of? Object
=> false
>> s.is_a? Object
=> true
>> s.is_a? Some
=> true
>> Some === s
=> true
>> Object === s
=> true
>>
之後會學到繼承,在Ruby中,所有的類別都是Object的子類別,因此所有物件都是一種Object。