讓我們繼續昨天的變數兄弟介紹吧
類別變數二哥,開頭是@@。對,就是大家愛用的的表情符號,不過它可不像表情符號可以隨便用。
類別變數存在class內,而且隨著class被繼承,還會傳遞到下一個class。
這樣講太抽象,來看個範例:
class Ball
@@diameter = 11 # 指定 11 給類別變數 @@diameter
def self.size #這邊用的是 class_method 的定義式
p "直徑是#{@@diameter}cm"
end
def length
p "直徑是#{@@diameter}cm"
end
end
Ball.size #=> "直徑是11cm"
Ball.new.length #=> "直徑是11cm"
先定義Ball
這個 class,以及能印出類別變數@@diameter
的類別方法size
。
呼叫Ball.size
,得到@@diameter
的值是11。
另外,類別變數也可以在實體方法內被呼叫,例如 Ball.new.length
Volleyball
繼承自Ball
,但是指定不同的值給@@diameter
看看會發生什麼事:
class Volleyball < Ball
@@diameter = 20
end
Volleyball.size # => "直徑是20cm"
Ball.size # => "直徑是20cm"
p @@diameter # => uninitialized class variable @@diameter in Object (NameError)
Volleyball.size
回傳 "直徑是20cm",很正常。(鄉民: 30cm才正常)
沒想到Ball.size
也得到"直徑是20cm"。
這時想在全域印出@@diameter
會噴錯,回答找不到這個類別變數。
到此可以歸納類別變數的特性: 存在class內,而且隨著class被繼承,還會傳遞到下一個class。
重要的是,只要是同一個繼承練上,類別變數就會被互相干擾,所以Effective Ruby作法15,才會說「寧用類別實體變數,莫碰類別變數」。
類別實體變數是三弟(實體變數)另一個身份,是類別
的實體變數
,相較於前面的實體變數有效範圍擴大到單ㄧclass
層級,又不像類別變數被整個繼承體系所共享,更符合我們的需求。像是OOP的特性之一:封裝,也不會希望class 之間互相污染。
上面的例子@@diameter
都改成類別實體變數@diameter
會如何?
class Ball
@diameter = 11
def self.size
p "直徑是:#{@diameter}cm"
end
end
Ball.size # => "直徑是:11cm"
class Volleyball < Ball
@diameter = 20
end
Volleyball.size # => "直徑是:12cm"
Ball.size # => "直徑是:11cm"
p @diameter # => nil
Volleyball.size
還是一樣,但是第二次Ball.size
保留了自己本來的變數值11
。原本互相的問題用類別實體變數就解決了。
全域變數大哥,開頭是$,全域都可呼叫,所以你如果在隱密的地方更改到全域變數,可能也不會發現,debug難度提高,大哥擁有薩諾斯一般的破壞力,謹慎使用。
何時會用全域變數呢? 像我們公司使用 Redis來快取資料,因為希望整個app都可以用到Redis,所以就將Redis的實體指定給全域變數
$redis = Redis.new(url: REDIS_URL)
但是後來我們公司導入rubocop,使用全域變數會跳出警告,所以我們改用其他方法來處理,這也說明了全域變數大哥是如同薩諾斯的存在,少用為妙。
還有一個全域變數的例子:
$LOAD_PATH #=> ["/Users/maxhuang/Desktop/Todolist/app/assets", "/Users/maxhuang/Desktop/Todolist/app/channels", ...
這是可以回傳 Gem 路徑的實體變數
總結一下變數四兄弟:
姓名 | 特徵 | 範例 | 預設值 |
---|---|---|---|
區域變數 | 無 | name | 無 |
實體變數 | 前面有@符號 | @name | nil |
類別變數 | 前面有@@符號 | @@name | 無 |
全域變數 | 前面有$符號 | $name | nil |
了解區域變數,實體變數,類別變數,全域變數,四兄弟的能力範圍後,以後就不容易碰到變數範圍的bug了。
像辦公室上週就遇到類似這個bug
if tag.preset? # => NameError (undefined local variable or method `tag' for main:Object)
tag = Tag.last
end
因為tag 還沒有被定義就被拿來用,Ruby 沒有像 JavaScript 會 Hoisting,這裡就會出現 NameError
的例外。