定義類別


在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_readerattr_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"]
>>
 

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>'
>>


在Ruby,定義於類別中的方法,預設會是公開(public),可以使用publicprivateprotected方法定義指定的方法為公開、私有或受保護,公開的方法可以直接指定物件接收者(包括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>'
>>


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
>>


之後會學到繼承,在Ruby中,所有的類別都是Object的子類別,因此所有物件都是一種Object