Class 是Ruby
很重要的觀念,要學習 Ruby
的一定要學會class & 物件。我們會在Day11-16 詳細講解何謂 class
。
以下為Day11
會提及的內容
除了無法單獨存在的Block
以外,所有的東西都是物件。
寫Ruby and Rails
一段時間後發現,在Rails的專案中,我們只能在定義models
, controllers
的class
裡面定義行為,不過大家有沒有想過,到底是哪個執行緒跑了這些class
? Ruby on Rails
替我們做了很多事情,只要我們遵循慣例走就好。
寫一段時間Rails
的朋友們,可能已經習以為常,但仔細想在專案裡面,下列的寫法有很多看點
Controller
不用寫建構子?<
在 Rails
為繼承,那被繼承的 ApplicationController
, ApplicationRecord
又是什麼?before_action
是如何被實作?# Controller: controllers/admin/order_controller.rb
module Admin
class OrdersController < ApplicationController
end
end
# Model: models/order.rb
class Order < ApplicationRecord
end
這系列的文章可能沒有辦法帶到太多Rails
的觀念,不過漢漢老師會不斷的在文章裡面提到自己使用Rails
的一些經驗
建構子就是在初始化的時候給定一些變數,以及基本的設定。constructor
,也有人叫做initializer
,所以只要看到這兩個英文單字就是在講建構子。以Ruby
程式語言來說的話,我們會在建構子初始化設定值在實體變數instance variable
。
以下方的車子為例,我們可以透過建構子指定車子的顏色、車子的大小。在 Ruby
中,建構子以initialize
表示。
class Car
def initialize(color, size)
@color, @size = color, size
end
end
red_car = Car.new('red', 'big') #=> #<Car:0x00007fd4ddc8ffa0 @color="red", @size="big">
blue_car = Car.new('blue', 'big') #=> #<Car:0x00007fd4ddc8c3c8 @color="blue", @size="big">
我們可以想像到,當我們初始化一個物件red_car
, blue_car
,Car
就像是工廠,red_car
, blue_car
y則是被產出來的物件,而工廠就是Car
類別,然而讀者也要清楚知道,Car
也是被創造的物件。在 Ruby 語言當中,所有物件的源頭都在Basic Object
。
介紹一個方法superclass
,透過superclass
我們可以找到 Ruby 物件的源頭
Car.superclass
#=> Object
Car.superclass.superclass
#=> BasicObject
Car.superclass.superclass.superclass
#=> nil
⭐️我們可以在建構子做計算
class DataSet
def initialize(ary)
@ary = ary
@size = ary.is_a?(Array) ? ary.count : 0
end
end
data_it = DataSet.new([1, 2, 3]) #=> #<DataSet:0x00007fd4ddcfd0f0 @ary=[1, 2, 3], @size=3>
我們可以在建構子設定參數的預設值。參數的預設值除了可以使用 ||
來表示以外,還可以在括號裡面填入方法名後面的參數填入預設值
#=> default value with || operator
class Animal
def initialize(*args)
@species, @examples = args[0] || '猩猩', args[1] || '小龐'
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4ddd2d980 @species="猩猩", @examples="小龐">
#=> default value with assigning arguments
class Animal
def initialize(species = '猩猩', examples = '小龐')
@species, @examples = species, examples
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4ddd56d58 @species="猩猩", @examples="小龐">
另外我們也說明,括號在Ruby裡面可有可無,舉例來說,下列兩種宣告新物件的方法相同,不過依照慣例,若沒有必要使用初始值,我們不用寫括號。
orangutan = Animal.new
orangutan = Animal.new()
⭐️ 在一般的程式語言中,預設參數的給法若在順序上有差異,使用上也會有差別。
class Animal
def initialize(species, examples = '小龐')
@species, @examples = species, examples
end
end
turtle = Animal.new('甲魚') #=> #<Animal:0x00007fd4ddd763b0 @species="甲魚", @examples="小龐">
⭐️ 不過Ruby
在某些狀況下會自己判斷預設值
class Animal
def initialize(species = '猩猩', examples)
@species, @examples = species, examples
end
end
orangutan = Animal.new('阿豪') #=> #<Animal:0x00007fd4dddb6550 @species="猩猩", @examples="阿豪">
⭐️ 宣告新物件時,括號可以省略
orangutan = Animal.new('阿豪')
orangutan = Animal.new '阿豪'
⭐️ 有些特殊物件的宣告是可以不用透過new
,像陣列、字串等。我們會比較偏愛literals constructor
的用法勝於使用一般建構子的宣告方式
# 這樣用會比較冗長,比較少人會這樣用
ary = Array.new
str = String.new
# literal constructor
ary = [1, 2, 3]
str = "qwer"
⭐️ 看一下如何使用 hash 做為參數宣告
class Animal
def initialize(species = '猩猩', examples:)
@species, @examples = species, examples
end
end
orangutan = Animal.new(examples: '阿豪') #=> #<Animal:0x00007fd4de0530d8 @species="猩猩", @examples="阿豪">
⭐️ Hash 搭配預設值
class Animal
def initialize(species = '猩猩', examples: '小龐')
@species, @examples = species, examples
end
end
orangutan = Animal.new
⭐️ 使用 options = {}
搭配建構子宣告
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
end
orangutan = Animal.new #=> #<Animal:0x00007fd4d3d1b8c0 @species="猩猩", @examples="小龐">
⭐️ 以下寫法會造成語法錯誤
# 語法錯誤 Predefined Args cannot allowed after named arg
class Animal
def initialize(species = '猩猩', examples: '小龐', options = {})
@species, @examples = species, examples
end
end
⭐️ 預設值搭配Hash
的使用方法要注意
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
end
#=> {:a=>"a"} 被當作 @species
#=> #<Animal:0x00007fd4d794d028 @species={:a=>"a"}, @examples="小龐">
orangutan = Animal.new(a: 'a')
# 預期內的行為
#=> #<Animal:0x00007fd4d9c8eb18 @species="猩猩", @examples="小龐">
orangutan = Animal.new('猩猩', a: 'a')
hash
預設值填在options
裡面,而若宣告新物件給a: 'a'
,原本的examples: '小龐'
將會被覆蓋。
class Animal
def initialize(species = '猩猩', options = {examples: '小龐'})
@species, @examples = species, options[:examples]
end
end
#=> #<Animal:0x00007fd4d9f67e70 @species="猩猩", @examples=nil>
orangutan = Animal.new('猩猩', a: 'a')
我們也可以使用splat operator
class Animal
def initialize(species = '猩猩', **options)
@species, @examples = species, options[:examples] || '小龐'
end
end
#=> #<Animal:0x00007fd4dd1195b8 @species="猩猩", @examples="小龐">
orangutan = Animal.new('猩猩', a: 'a')
block
可以當作變數使用
class Animal
def initialize(species = '猩猩', it_day11)
@species, @block = species, it_day11
end
end
orangutan = Animal.new('猩猩', -> (x) {x + 1})
#=> #<Animal:0x00007fd4db77f558 @species="猩猩", @block=#<Proc:0x00007fd4db77f5a8@(irb):164 (lambda)>>
在Ruby
的世界中,我們可以使用getter
, setter
取得與改寫值。當我們想要取得動物的種類找不到,是因為我們沒有給方法取得值。
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
end
orangutan = Animal.new
orangutan.species # NoMethodError (undefined method `species' for #<Animal:0x00007fd4dcad19a8>)
⭐️ 我們可以透過一些方法取得值
# 法1. 透過 instance_eval 取得值
orangutan.instance_eval { @species } #=> 猩猩
orangutan.instance_eval { species } #=> 猩猩
# 法2. 對 orangutan 加入 getter 的單體方法,使其可以取用值
class << orangutan
def species
@species
end
end
# 可以使用 getter
orangutan.species #=> 猩猩
⭐️ 接著開始介紹getter
,以下為 getter
的使用方式
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
def species
@species
end
end
orangutan = Animal.new
orangutan.species #=> 猩猩
⭐️Ruby
的getter
,有更簡潔的寫法
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
attr_reader :species
end
orangutan = Animal.new
orangutan.species #=> 猩猩
⭐️ 我們可以利用setter
來去設定實體變數
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
# getter
attr_reader :species
# setter
def species=(species)
@species = species
end
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️Ruby
也有更簡潔的用法
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
# getter
attr_reader :species
# setter
attr_writer :species
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️ 對於取值、改寫值的用法,Ruby
提供了 attr_accessor
作為getter
, setter
使用。
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
# getter/setter
attr_accessor :species
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> 猩
⭐️ 若要對getter, setter 進行客製化功能的話,只能夠自己寫
class Animal
def initialize(species = '猩猩', options = {})
@species, @examples = species, options[:examples] || '小龐'
end
attr_reader :species
# setter
def species=(species)
@species = "❌ #{species}"
end
end
orangutan = Animal.new
orangutan.species = '猩'
orangutan.species #=> "❌ 猩"
下列的例子中,drive
為物件的方法,稱為instance method
class Car
def initialize(color, size)
@color, @size = color, size
end
def drive
[color.capitalize, 'car', 'boo boo!'].join(' ')
end
private
attr_reader :color
end
red_car = Car.new('red', 'big')
blue_car = Car.new('blue', 'big')
red_car.drive #=> "Red car boo boo!"
blue_car.drive #=> "Blue car boo boo!"
首先要澄清兩件事情:
若不寫attr_reader
一樣可以動作。
color
為self.color
的簡寫,這邊我們改使用實體變數@color
也可以。個人習慣在唯讀的變數加入attr_reader
,並且毋需在外部取得值的地方,例如上例的@color
,漢漢老師就會把它寫在private
裡面,避免外部可以取得值。
在Ruby
的世界中,一共有public
, private
, protected
三種方法,目前還沒有用過protected
過,還沒有碰過需要區分的情境。若好奇的參考 這篇 論述即可。
class Car
# public 方法
private
# 私有方法
protected
# 保護方法
end
由analyse_color
可以得知,self.變數
等於實體變數
。此外,object_id
可以用來判斷物件實際的位址。
class Car
def initialize(color, size)
@color, @size = color, size
end
def reflect
self
end
def analyse_color
color.object_id == @color.object_id
end
end
red_car.reflect #=> #<Car:0x00007fd065a07520 @color="red", @size="big">
red_car.reflect == red_car #=> true
red_car.analyse_color
實體變數為長相@
開頭的變數,都為實體變數instance variable
。上面已經提到了很多實體變數的觀念,這邊會再詳述實體變數的概念。
@color, @size, @species, @examples
實體變數是內部存放資料的地方,我們可以利用實體變數來取值和給值,並且我們不一定要在初始化的時候宣告實體變數。下例即為令新的實體變數@age
,並給他get
, set
兩種方法(複習一下attr_accessor
)
class Car
def initialize(color = 'red', size = 'big')
@color, @size = color, size
end
attr_accessor :age
end
car = Car.new
car.age = 20
car.age #=> 20
我們實際看 car
物件的實體變數
car.instance_variables
#=> [:@color, :@size, :@age]
這邊花了很大的篇幅,介紹了class
的基本用法,後面的章節會介紹類別方法。
如今已經到了第11天,看著訂閱數與觀看數的微幅增長,確實比起跟著股票的漲跌而心情起舞,感受還要好。看到有人願意花時間點開我的文章,我真的很開心。
第一天列出了這一期鐵人賽的大綱,在寫的過程中一直改變想法,30篇的文章架構不斷的再改變。一來是下班時間並沒有那麼多,加上若一篇文章訊息量太大,反而會造成反效果。所以這陣子不斷的調整文章,調整能夠讓讀者看到更精闢的重點!
我也推薦同樣是工程師的朋友們寫文章,雖然表面上好像很花時間,比起自己在邊工作的時候,只記自己看得懂的筆記。多用心記錄,整理成主題式的文章,未來再反芻自己的文章也比較不會看不懂,其實算是很好的投資。