透過類別(class)我們可以組織和產生許多具有相似屬性(attributes)和方法(methods)的物件。
# CheatSheet
class Customer
attr_reader :id, :name
attr_writer :id, :name
attr_accessor :id, :name
def initialize(id, name)
@id = id
@name = name
end
def self.greeting
end
private
protected
end
類別(class)
慣例上來說 class 會以大駝峰的方式命名(CamelCase ),例如 NewClass。
定義類別
- 使用關鍵字
class
來建立類別 - 透過關鍵字
initialize
來建立初始化物件的方法 - 以
@
開頭的方式建立「實例變數instance variable
」,每一個透過這個 class 建立的物件都會有自己的實例變數
class [ClassName]
# 在這裡定義 class variable
@@class_variable = [class_vairable]
# 在 initialize 中的程式會在 instantiation 的過程先被執行
def initialize([arg1], [arg2], ...)
# 一般來說 arguments 和 instance variable 的名稱會一樣
@arg1 = arg1
@arg2 = arg2
end
# Do something here ...
# instance method with instance variable,建立物件時就要放入參數
def get_custom_info
"id: #{@id}, name: #{@name}, addr: #{@addr}"
end
# instance method without instance variable,可以在建立物件後再放入參數
def greeting(name)
"Hello, #{name}. Welcome to our store"
end
end
透過類別建立物件
使用 Class.new('argument')
的方式來建立物件
class Person
def initialize(name)
@name = name
end
end
matz = Person.new('Yukihiro')
類別中的方法(methods in class)
類別中的方法主要可以分成實例 方法(instance methods)
和類別方法(class methods
):
- 實例方法(instance method):能夠在實例上被使用的方法,大部分類別中的方法都是屬於實例方法。
- 類別方法(class method):能夠在類別(class)上被使用的方法
實例方法(instance method)
- 在 class 中直接建立的 method 就是 instance method
- 它可以被該 class 所建立的 instance 所使用,亦可代入參數
class Sample
# 這是實例方法(instance method)
def hello(name)
puts "Hello #{name}"
end
end
# use class to create objects with the method
instance = Sample.new # create a object with "new"
puts instance.methods # list all the method in the class
instance.hello "Kitty" # 這個 hello method,直接作為於實體 instance 上,稱作 instance method。
類別方法(class method)
- class method 指的是可以作用在 class 上的 method
# 這裡 .all 是作用在 Post class 上,所以稱作 class method
class PostsController < ApplicationController
def index
@posts = Post.all # all 是一個 class method
end
end
使用關鍵字 self
建立 class method:
###
# 透過 self 可以建立屬於該 class 的 method(class method)
# self 指的就是該物件本身(Hello)
# self.sayhi_class 等同於 Hello.sayhi_class
###
class Hello
def self.sayhi_class(name)
return "Hello, #{name}"
end
def sayhi_instance(name)
return "Hello, #{name}"
end
end
使用類別方法:
###
# :sayhi_class 直接作用在 Hello class 上,是 class method
# :sayhi_instance 作用在 Hello class 所建立的 greet instance 上,是 instance method
###
puts Hello.sayhi_class("Aaron") # class method
greet = Hello.new
puts greet.sayhi_instance("Aaron") # instance method
p greet.respond_to?(:sayhi_instance) # true,greet 中有方法 sayhi_instance(檢查該物件是否包含該方法)
p greet.respond_to?(:sayhi_class) # false,greet 中沒有方法 sayhi_class
p greet.class # Hello,greet 的 class 是 Hello
p greet.class.respond_to?(:sayhi_instance) # false,Hello 中沒有 sayhi_instance
p greet.class.respond_to?(:sayhi_class) # True,Hello 中有 sayhi_class
# puts greet.method(:sayhi_instance) # 檢查該物件的方法來自哪裡
保護所定義的 instance method
在 Ruby 中通常會在最前面加上 #
來表示它是個 instance method,有三種層度可以保護 instance method(對於 instance variable 或 class 則沒有保護)
Public Method
:可以在程式中被呼叫(類似公開的 API),除了 initialize 之外,其他的 methods 預設都是 public。Private Method
:在 class 的外面無法被存取,只有在 class 內可以提取 private 的方法。Protected Method
:只能被所定義的 class 和它的 subclass 所呼叫。
使用方法
我們可以使用關鍵字 private
和 protected
來保護所定義的方法:
###
# 第一種寫法:
# 在最後定義哪些是 private 或 protected
###
private :[method_name1], :[method_name2]
protected :[method_name1], :[method_name2]
###
# 第二種寫法:
# 在 private 或 protected 關鍵字後的都是某類型的 instance method
###
class ClassName
def initializa(param)
@param = param
end
public
def public_method; end
private
def private_method; end
protected
def protected_method; end
end
範例
class Box
def initialize(w,h)
@width = w
@height = h
end
# instance method 預設是 public
def getArea
getWidth * getHeight
end
def getWidth
@width
end
def getHeight
@height
end
# 透過關鍵字 'private' 設定方法為 private 狀態
private :getWidth, :getHeight
def printArea
@area = getWidth * getHeight
puts "Big box area is: #@area"
end
# 透過關鍵字 'protected' 設定方法為 private 狀態
protected :printArea
end
使用上述方法的結果:
block_area = Box.new(200,300)
block_area.getArea # 60000
block_area.getWidth # private method `getWidth'
block_area.printArea # protected method `printArea'
setter/getter: 取得或設定 class 中的 instance variable
因為 Ruby 並沒有「屬性」(property/attribute)這樣的東西,要取用實體變數,需要另外定義的方法才行:
getter
#######################################################
# 建立取得 instance variable 的方法 ##
# accessor/getter method ##
#######################################################
class Customer
def initialize(id, name)
@id = id
@name = name
end
# 建立取得 instance variable 的方法
# 方法的名稱通常會和 arguments, instance variable 一樣。
# 可以使用 attr_reader 快速取得
def id
@id
end
def name
@name
end
end
attr_reader
為了避免寫上面落落長一大段,我們可以使用 attr_reader
來快速設定 getter
:
##
# 透過 attr_reader 可以快速建立取得 instance variable 的方法
# 不一定要放在 initialize method 前面
##
class Customer
attr_reader :id, :name
def initialize(id, name)
@id = id
@name = name
end
end
# 取的 instance variable 的值
aaron = Customer.new(1, "Aaron", "Tainan")
p aaron.id # 1
p aaron.name # "Aaron"
setter
class Customer
attr_reader :id, :name, :addr
def initialize(id, name, addr)
@id = id
@name = name
@addr = addr
end
# 更改/設定 instance variable 的方法
def id=(value)
@id = value
end
def name=(value)
@name = value
end
def addr=(value)
@addr = value
end
end
attr_writer
為了避免寫上面落落長一大段的 setter
,我們可以利用attr_writer
快速設定 instance variable:
class Customer
attr_reader :id, :name, :addr # 透過 attr_reader 可以快速取得 instance variable
attr_writer :id, :name, :addr # 透過 attr_writer 可以快速設定 instance variable
def initialize(id, name, addr)
@id = id
@name = name
@addr = addr
end
end
aaron = Customer.new(1, "Aaron", "Tainan")
p aaron.id # 1
aaron.id = 2
p aaron.id # 2
attr_accessor:同時建立 setter 和 getter
我們可以使用 attr_accessor
同時建立變數的 setter 和 getter:
class Customer
attr_accessor :id, :name, :addr # 等於同時寫了 attr_reade 和 attr_writer
def initialize(id, name, addr)
@id = id
@name = name
@addr = addr
end
end
類別的融合(開放類別)
在 Ruby 裡,如果遇到兩個一樣名字的類別,並不會「覆蓋」,而是會進行「融合」,因此如果我們不小心定義了相同名稱的類別:
class Cat
def abc
# ...
end
end
class Cat
def xyz
# ...
end
end
kitty = Cat.new
kitty.abc # => 會發生什麼事?
kitty.xyz # => 會發生什麼事?
實際上的結果會是這樣:
class Cat
def abc
# ...
end
def xyz
# ...
end
end
為原本的類別添加方法
也因此,利用這個特性,可以為原本的 String class 添加方法,這個技巧稱之開放類別(Open Class)。:
class String
def say_hello
"hi, I am #{self}"
end
end
puts "eddie".say_hello # => hi, I am eddie
puts "kitty".say_hello # => hi, I am kitty
繼承(inheritance)
在物件導向的概念裡,通常會把相同功能的方法移到上一層的類別裡,然後再去繼承它。
可以使用關鍵字 <
來表示「繼承自」,寫 Child < Parent
表示 child 繼承自 parent,這時 Child 這個 class 被稱作 subclass
,Parent 這個 class 則被稱作 parent
或 superclass
。
class Animal
def eat(food)
puts "#{food} 好好吃!!"
end
end
# Cat 和 Dog 是不同 class,但都具有 eat method
class Cat < Animal
end
class Dog < Animal
end
在 Ruby 中任何的類別都只會有一個 superclass,因此不會有 multiple inheritance
的情況,下面這種 multiple inheritance 的情況會報錯:
class Creature
def initialize(name)
@name = name
end
end
class Person
def initialize(name)
@name = name
end
end
##
# ERROR: superclass mismatch
##
class Dragon < Creature; end
class Dragon < Person; end
覆蓋(override)繼承來的方法
當 Dragon 繼承自 Creature 時,可以覆蓋掉 Creature 中原本的方法:
class Creature
def initialize(name)
@name = name
end
def fight
return "Punch to the chops!"
end
end
# Add your code below!
class Dragon < Creature
def fight
return "Breathes fire!"
end
end
使用 super 關鍵字來呼叫父層類別中的方法
我們可以使用 super([arg1, arg2, ...])
來直接呼叫父層中同名的方法:
class Creature
def initialize(name)
@name = name
end
def fight(number)
return "Punch to the chops! #{number}"
end
end
class Dragon < Creature
def fight
puts "Instead of breathing fire..."
super(2) # 等於在這裡呼叫 Creature 中的 fight
end
end
dragon = Dragon.new('kuku')
dragon.fight
建立 .to_s 的實例方法
任何一個你所定義的 Class 都應該要有一個 to_s 的 instance method,目的是要以字串的形式回傳 object 的內容。
要留意的是,當我們在使用 puts 時,會自動代入 to_s 這個方法,因此在 class 中建立 to_s 方法會覆蓋掉原本的 to_s。
#################################
# The to_s method ##
#################################
class Customer
attr_accessor :id, :name, :addr # 等於同時寫了 attr_reade 和 attr_writer
def initialize(id, name, addr)
@id = id
@name = name
@addr = addr
end
def to_s
"This class contains (instance) variables include #{@id}, #{@name}, #{@addr}"
end
end
aaron = Customer.new(1, "Aaron", "Tainan")
##
# puts 會自動代入.to_s,因此會回傳
# "This class contains (instance) variables
# include 1, Aaron, Tainan"
##
puts aaron
其他
在 Class 裡面的 instance variable (@)
應用在字串時,可以直接寫 #@
。
a = 30
p "The number is #{a}"
class People
def initialize(name, age)
@name = name
@age = age
end
def show_age
return "#@name is #@age"
end
end
aaron = People.new("Aaron", 28)
p aaron.show_age
物件(object)
在 Ruby 中幾乎所有東西都是物件,即使是 class 本身也是 Class 這個 class 的物件(但 block
不是)。
物件(object)是類別(class)的實例(instance),我們可以透過 <class>.new
這個關鍵字來建立實例物件(instance object),這個過程稱做 instantiation
(instantiate a class)。
另外,我們可以透過 object.class
來檢視這個物件屬於哪個類別。
物件的輸出
可以用 pp
, yaml
, 或 inspect
:
#########################
# 輸出物件 ##
#########################
require 'pp'
require 'yaml'
class User
attr_accessor :name, :age
end
user = User.new
user.name = "John Smith"
user.age = 30
puts user.to_yaml
puts pp(user)
######################### # 輸出 JSON ## #########################
<?
require 'json'
puts JSON.pretty_generate(JSON.parse(object.to_json))
?>
模組(Module)
觀念
你可以把模組想成是包含許多方法和常數的工具箱。模組其實和類別的概念很相似,但模組不能建構實例(instance),而且也不會有子類別(subclass),它只是用來儲存東西的。
在模組中不太會包含變數(variable),因為變數可能會變動。相反地,因為常數(constant)不會變動,所以我們可以把一些固定的常數存在模組中。
scope resolution operator
模組有一個很重要的功能就是將許多不同的方法和常數分入不同的命名空間(name space
),稱作 namespacing
,因此 Ruby 將不會無法區別 Math::PI
和 Circle::PI
。
這裡的 ::
稱作 scope resolution operator
,它告訴 Ruby 可以在哪裡去找到對應的程式碼,例如,Math::PI
表示在 Math module 裡的方法 PI
。
建立模組
我們可以透過關鍵字 module
來建立模組,命名規則與 class 相同,使用駝峰大寫
## 透過 module 建立模組
module Flyable
FAVEROITE_DRINK = 'Spring tea'
def self.feature
puts "Fly"
end
def fly
puts "I can fly!"
end
end
引用模組到環境中
有些內建的模組並沒有預設載入到環境中,因此當我們想要使用這個模組時,我們可以使用關鍵字 require
,記得引入的模組名稱要加上分號:
# require 'module'
require 'data'
引用模組到類別中
如果我們想要把 module 引用到 class 中時,我們可以使用關鍵字 include
或 extend
。
include
在 class 中當我們想要載入模組時,使用關鍵字 include
,就可以讓這個 class 的實例擁有該 module 所定義的方法:
class Dog
##
# 透過 include 在類別中掛上模組
##
include Flyable
def initialize(name)
@name = name
end
def bark
puts 'Bark! Bark!'
end
def favorite_drink
puts FAVORITE_DRINK
end
end
little_yellow = Dog.new('litter_yellow')
##
# 透過 include 掛入的實例方法
##
little_yellow.fly
little_yellow.favorite_drink
extend
透過 include
我們可以把 module 中的方法在實例中被使用;透過 extend
則是可以把 module 中的方法在類別中被使用。
module ThePresent
def now
puts "It's #{Time.new.hour > 12 ? Time.new.hour - 12 : Time.new.hour}:#{Time.new.min} #{Time.new.hour > 12 ? 'PM' : 'AM'} (GMT)."
end
end
class TheHereAnd
extend ThePresent
end
##
# 透過 extend 掛入的類別方法
##
TheHereAnd.now
呼叫使用模組
##
# 在實例中使用 Module
##
little_yellow = Dog.new('litter_yellow')
little_yellow.bark
little_yellow.fly
##
# 直接使用 Module 的類別方法
##
puts Flyable.feature
puts Math::PI
程式碼範例
## 透過 module 建立模組
module Flyable
FAVORITE_DRINK = 'Spring tea'
def self.feature
puts "Fly"
end
def fly
puts "I can fly!"
end
end
class Dog
##
# 透過 include 在類別中掛上模組
##
include Flyable
def initialize(name)
@name = name
end
def bark
puts 'Bark! Bark!'
end
def favorite_drink
puts FAVORITE_DRINK
end
end
little_yellow = Dog.new('litter_yellow')
little_yellow.bark
little_yellow.fly
little_yellow.favorite_drink
混合(mixin)
當我們透過在 class 中加入 module,讓它可以擁有更多混合的行為和訊息時,我們把它稱作 mixin
。透過 mixin 我們可以客制化 class 而不需要重寫程式碼。
參考資料
- Ojbect @ Ruby-doc
- Codecademy
- Treehouse
- 為你自己學 Ruby on Rails