ぎょーぼのぶろぐ

IT系の話を書いていくブログです。今はRubyの勉強中。

【Ruby v2.6】クラス変数 その1(基本的なこと)

前置き

クラス変数について、まとめてみます。個人的には、インスタンス変数、クラスインスタンス変数より注意が必要な感じがしています。

その1は、基本的なこと。

クラス変数

クラス変数は、そのクラス、およびそのクラスから継承されたクラスで共有される変数です。生成したクラス、そのクラスから継承されたクラスのクラスメソッド、インスタンスメソッドのどこからでも参照、値の変更ができます。

継承の階層が深くなったり、インスタンスが増えてきたりすると、どこで値が変更されたかが分かりづらくなるのが欠点です。

使い方

クラス定義の中で、変数名の前に@@をつけて、値を代入したタイミングで生成されます。通常はメソッド定義の外で生成しますが、クラスメソッド内、インスタンスメソッド内で生成することもできます。

初期化せずに参照しようとすると、nil を返すのではなく、NameError 例外を返します。

基本的なクラス変数

以下のようなクラス A 、クラス B で確認してみます。

class A
    @@clsval = "@@classval"

    def self.class_method_a
        p "#{@@clsval} : class A  self.class_method_a "
    end

    def ins_method_a
        p "#{@@clsval} : class A  ins_method_a "
    end
end

class B < A
    def self.class_method_b
        p "#{@@clsval} : class B  self.class_method_b "
    end

    def self.change_clsval
        @@clsval = @@clsval + " changed!!! "
    end

    def ins_method_b
        p "#{@@clsval} : class B  ins_method_b "
    end
end

各クラスのクラス変数を確認してみます。

A.class_variables    => [:@@clsval]
B.class_variables    => [:@@clsval]

クラス定義を読み込んだ直後は、クラス A で生成されているのは @@clsval のみです。また、クラス A を継承しているクラス B でも@@clsvalが見えます。

class_variable メソッドは、そのクラスで使用できるクラス変数が表示されてしまうので、要注意です。このサンプルだと、@@clsval は、クラス A が大元のクラス変数だとすぐにわかりますが、コードが長く、複雑になってくると、クラス変数の大元がどのクラスなのか、分かり辛くなります。

続いて、各メソッドで、@@classval を参照してみます。

A.class_method_a     # => "@@classval : class A  self.class_method_a "
B.class_method_b     # => "@@classval : class B  self.class_method_b "
obj_a = A.new
obj_a.ins_method_a   # => "@@classval : class A  ins_method_a "
obj_b = B.new
obj_b.ins_method_b   # => "@@classval : class B  ins_method_b "
obj_b.ins_method_a   # => "@@classval : class A  ins_method_a "

クラス A のクラスメソッド、インスタンスメソッド、クラス B のクラスメソッド、インスタンスメソッド、継承されたインスタンスメソッドの全てで、@@classval にアクセスできています。

クラス B のメソッドで、@@clsval の値を変更してみます。

B.change_clsval       # => 値に" changed!!! "を追加

A.class_method_a      # => "@@classval changed!!!  : class A  self.class_method_a "
B.class_method_b      # => "@@classval changed!!!  : class B  self.class_method_b "
obj_a.ins_method_a    # => "@@classval changed!!!  : class A  ins_method_a "
obj_b.ins_method_b    # => "@@classval changed!!!  : class B  ins_method_b "

値を変更すると、クラス A、クラス B のどこから参照しても、値が変わっています。

その1のまとめ

  • クラス変数は、そのクラスと継承したクラスの全体で共有する変数。
  • クラス定義内のメソッド外でも、クラスメソッド内でもインスタンスメソッド内でも、代入して初期化したタイミングで生成される。が、生成する場合は必ずメソッド外で初期化するようにすることを強く推奨する
  • 代入(初期化)しないで参照しようとすると、NameError 例外が発生する。
  • 影響範囲のクラスであれば、どのメソッドからも参照ができ、書き換えも可能。

どこからでも参照、書き換え可能と言われると、変数のスコープを意識しなくていいので、すごく使いやすそうな感じがしますが、どこからでも制限なく書き換えができてしまうので、コードが長く、複雑になってくると、どこからでも書き換えが可能なことがバグの原因となってしまい、デバッグも難しくなってしまうことがあります。基本的には、クラス変数は使わずに、クラスインスタンス変数などの他の手段で変数管理をする方が、予想外の事故を減らせるように思います。

また、初期化するのは必ずメソッド外で行いましょう。メソッドの中で初期化すると、特にクラス継承が絡んでくるととてもややこしい挙動をします。

その2に続きます。