【Ruby v2.6】クラス変数 その2(メソッドで生成した場合)
前置き
クラス変数についてまとめます。 その2は、クラス変数をメソッド内で生成した場合の挙動について。
クラス変数のメソッド内生成
その1でも書きましたが、クラス変数はメソッド外だけでなく、クラスメソッドやインスタンスメソッド内でも生成することができます。
一瞬、便利な感じがしますが、「継承」が絡んでくると、クラス変数がどのクラスで生成されるのか、とても分かり辛くなるので要注意です。
例
以下のようなクラスを作ってみます。
class A def self.class_method_a1 @@a1 = "created by class method a1 in A." end def self.class_method_a2 @@a2 = "created by class method a2 in A." end def self.class_method_a3 @@a3 = "created by class method a3 in A." end end class B < A def self.class_method_a2 @@a2 = "created by class method a2 in B." end end class C < B def self.class_method_a3 @@a3 = "created by class method a3 in C." end end
クラス A では、class_method_a1
、class_method_a2
、class_method_a3
の3つのクラスメソッドを定義し、それぞれでクラス変数を生成しています。
クラス B では、クラス A を継承し、class_method_a2
だけをオーバーライドして、オーバーライド前と同じクラス変数 @@a2
を生成しています。
クラス C では、クラス B を継承し、こちらは class_method_a3
をオーバーライドして、オーバーライド前と同じクラス変数 @@a3
を生成しています。
この状態で、クラス A のクラスメソッド3つを呼び出してみます。
A.class_method_a1 # => "created by class method a1 in A." A.class_method_a2 # => "created by class method a2 in A." A.class_method_a3 # => "created by class method a3 in A." A.class_variables # => [:@@a1, :@@a2, :@@a3] B.class_variables # => [:@@a2, :@@a1, :@@a3] C.class_variables # => [:@@a2, :@@a1, :@@a3]
クラス A で @@a1
、 @@a2
、 @@a3
が生成され、継承しているクラス B、クラス C でも認識されています。
・・・しかし・・・
一度リセットして、クラス A のクラスメソッドを呼び出さずに、クラス B、クラス C でオーバーライドしたクラスメソッドを呼び出してみます。
B.class_method_a2 # => "created by class method a2 in B." C.class_method_a3 # => "created by class method a3 in C." A.class_variables # => [] B.class_variables # => [:@@a2] C.class_variables # => [:@@a3, :@@a2]
今度は、クラス A では @@a2
、 @@a3
が認識されていません。クラス B でも、クラス C でオーバーライドしたメソッドで生成した @@a3
が認識されていません。
・・・さらに・・・
もう一度リセットして、クラス C でオーバーライドしていないクラスメソッドを呼び出してみます。
C.class_method_a1 # => "created by class method a1 in A." C.class_method_a2 # => "created by class method a2 in B." A.class_variables # => [:@@a1] B.class_variables # => [:@@a2, :@@a1] C.class_variables # => [:@@a2, :@@a1]
class_method_a1
は、クラス A からそのまま継承されているので、@@a1
はクラス A で生成されていますが、class_method_a2
の方は、クラス B でオーバーライドしているので、クラス B のクラスメソッドとして実行され、@@a2
はクラス B のクラス変数になってしまっています。
つまり、定義がどのクラスに書かれているか、は関係なく、クラス変数の生成(初期化)が実行されたクラスのクラス変数となる、ということになります。
さらにややこしいこととして、クラス C でクラス変数を生成後、クラス A で同じクラス変数を生成すると。
C.class_method_a3 # => "created by class method a3 in C." C.class_variable_get :@@a3 # => "created by class method a3 in C." A.class_variables # => [] C.class_variables # => [:@@a3] A.class_method_a3 # => "created by class method a3 in A." C.class_variable_get :@@a3 # => "created by class method a3 in A." A.class_variables # => [:@@a3] C.class_variables # => [:@@a3]
クラス C のクラス変数だった @@a3
が、クラス A のクラス変数に置き換わってしまいます。
このように、クラスの継承やメソッドのオーバーライドが絡んでくると、どこで生成されたクラス変数か分かり辛くなり、クラスメソッドの実行タイミングによっても、クラス変数の影響範囲が変わってしまう、ということになってしまいます。
どうしたらいいか?
ですので、その1でも書いたように、クラス変数の場合はクラス定義内の「メソッドの外」で必ず初期化(生成)を行うことを強く推奨します。これだけで、「継承」の問題は解決できます。