類別變數


接續 單例方法、實例方法、類別方法 的討論。

個別類別可以擁有自己的類別方法,也可以擁有自己的類別變數(Class variable),只要變數前加上@@符號就可以定義類別變數。例如:
>> class Some
>>     @@s = 10
>>     def Some.s
>>         @@s
>>     end
>>     def Some.s=(value)
>>         @@s = value
>>     end
>> end
=> nil
>> Some.s
=> 10
>> Some.s = 20
=> 20
>> Some.s
=> 20
>>


上例中定義了Some的類別變數@@s,預設外界是不可以直接存取類別變數,你必須為類別變數定義類別方法,外界才可存取,如上例所示。

可以在實例方法中使用類別變數,不過,你不能 類別方法中使用實例變數,因為類別方法屬於類別擁有,但實例變數屬於實例擁有,類別方法中無法單就@name來識別該取得哪個實例的變數值:
>> class Some
>>     def initialize(value)
>>         @value = value
>>     end
>>     def Some.doValue
>>         @value += 1
>>     end
>> end
=> nil
>> s = Some.new(10)
=> #<Some:0x1df6658 @value=10>
>> Some.doValue
NoMethodError: undefined method `+' for nil:NilClass
        from (irb):6:in `doValue'
        from (irb):9
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


在頂層範圍中,如果你如下定義:
X = 10

相當於如此定義:
class Object
    X = 10
end

這可以如下證實:
>> X = 20
=> 20
>> X
=> 20
>> Object::X
=> 20
>>


類似地,如果你如下定義:
y = 5
@@y = 10


相當於如下定義:
class Object
    y = 5
    @@y = 10
end

這可以如下證實:
>> @@y = 10
=> 10
>> y = 10
=> 10
>> def show_y
>>     puts y
>> end
=> nil
>> show_y
NameError: undefined local variable or method `y' for main:Object
        from (irb):35:in `show_y'
        from (irb):37
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> def show_aay
>>     puts @@y
>> end
=> nil
>> show_aay
10
=> nil

>> def Object.y
>>     @@y
>> end
=> nil
>> Object.y
=> 10
>>


變數範圍 談過,區域變數的範圍就是該區塊,因此上例中y是不能在show_y中可見的。

類別既然為Class實例,而類別方法實際上為Class實例上的單例方法,那麼類別變數就是Class實例的實例變數嗎?答案是否定的!Class實例還是可以擁有自己的實例變數。例如:
>> class Some
>>     @@s = 10
>>     def Some.s
>>         @@s
>>     end
>>     def Some.s=(v)
>>         @@s = v
>>     end
>> end
=> nil
>> Some.s = 10
=> 10
>> Some.s
=> 10
>> def Some.instance_s
>>     @s
>> end
=> nil
>> def Some.instance_s=(v)
>>     @s = v
>> end
=> nil
>> Some.instance_s = 20
=> 20
>> Some.instance_s
=> 20
>> Some.s
=> 10
>>


@@開頭的變數之所以稱為類別變數,是因為它定義了類別範圍內可見的變數,而不是定義Class實例的實例變數。注意,@@開頭的變數是定義在哪個類別中,它的可視範圍就是在該類別。例如以下是可行的:
>> class Some
>>     @@s = 10
>>     def Some.s
>>         @@s
>>     end
>> end
=> nil
>> Some.s
=> 10
>>


@@s是定義在Some中,所以在整個Some中都是可見的,不過以下這個範例有個陷阱:
>> class Some
>>     @@s = 10
>> end
=> 10
>> def Some.s
>>     @@s
>> end
=> nil
>> Some.s
NameError: uninitialized class variable @@s in Object
        from (irb):5:in `s'
        from (irb):7
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


Some中的@@s確實是指在Some中可見的類別變數,但是你在頂層定義Some.s時,實際上是等同於:
class Object
    ...
    def Some.s
        @@s
    end
end

這時的@@s是想取得Object中的@@s,當然就看不到而發生錯誤,注意以上的NameError中的訊息,就是uninitialized class variable @@s in Object,而不是uninitialized class variable @@s in Some

再來看個有趣的實驗:

>> class Some; end
=> nil
>> def Some.x=(v)
>>     @@x = v
>> end
=> nil
>> def Object.x
>>     @@x
>> end
=> nil
>> Some.x = 10
=> 10
>> Object.x
=> 10
>> @@x
=> 10
>>


定義在Some.x中的@@x,經由Object.x中的@@x取得的值是相同的。

注意,在頂層或者說是Object類別中,若有@@變數與內部類別的@@變數同名,則外部類別的@@變數會覆蓋內部類別的@@變數。例如:
>> @@x = 10
=> 10
>> class Some
>>     @@x = 20
>> end
=> 20
>> @@x
=> 20
>>


這相當於:
>> class Object
>>     @@x = 10
>>     class Some
>>         @@x = 20
>>     end
>> end
=> 20
>> @x
=> nil
>> @@x
=> 20
>>


別以為如下就是設定Some的類別變數:
>> class Some; end
=> nil
>> def Some.x=(v)
>>     @@x = v
>> end
=> nil
>> def Some.x
>>     @@x
>> end
=> nil
>> Some.x = 10
=> 10
>> Some.x
=> 10
>> @@x
=> 10
>>


但是在非頂層或Object中的行為則不同,外部類別的@@變數「不會」覆蓋內部類別的@@同名變數。例如:
>> class Some
>>     @@x = 10
>>     class Other
>>         @@x = 20
>>     end
>>     puts @@x
>> end
10
=> nil
>>