ぎょーぼのぶろぐ

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

【Ruby v2.6】クラスメソッド その2(クラスメソッドと特異クラス)

前置き

Ruby のクラスメソッドについてです。その2は、クラスメソッドと特異クラスについて。

クラスメソッド

その1で、クラスメソッドの定義方法は、self.[メソッド名] で定義する、と書きましたが、これは、インスタンスオブジェクトに特異メソッドを定義する場合と同じ書き方 [オブジェクト名].[メソッド名] です。つまり、クラスメソッドは、クラスオブジェクトに対する特異メソッド、ということになります。

サンプル

以下、サンプルのクラスで例を示します。

class A
    # クラスメソッド
    def self.class_method
        p "called self.class_method."
    end
end

まず、通常のクラス A の定義ですが、調べてみると、

A.methods(false)                              # => [:class_method]
A.singleton_class.instance_methods(false)     # => [:class_method]

クラスメソッド class_method は、クラス A のメソッドであると同時に、クラス A の特異クラスのインスタンスメソッド、ということになっています。

「クラスメソッド」と「特異クラス」の関係

図で表現すると、下記のようになります。

f:id:gyobo:20200817220924p:plain

クラスメソッド :class_methodを定義すると、図のようにクラス A の特異クラスのインスタンスメソッドとして、:class_method が格納されます。

クラスメソッドのもう一つの定義方法

クラスメソッドは、特異クラスのインスタンスメソッドです。ですので、クラスメソッドのもう一つの定義方法として、特異クラスに直接、インスタンスメソッドとして定義する方法があります。

class B
    # 特異クラスを開く
    class << self
        # 特異クラスのインスタンスメソッドとして定義する。
        def class_method_2
            p "called class_method_2."
        end
    end
end

このクラスについて確認してみると、

B.methods(false)        # => ":class_method_2]
B.class_method_2        # => "called class_method_2."

確かに、クラス B のクラスメソッドとして定義されています。

その2のまとめ

  • クラスメソッドは、クラスオブジェクトの特異メソッドであり、そのクラスの特異クラスのインスタンスメソッドでもある。
  • クラスメソッドを定義すると、そのクラスの特異クラスに格納される。
  • クラスメソッドの定義方法として、「特異クラス」のインスタンスメソッドとして定義する方法もある。

その3に続きます。

【Ruby v2.6】クラスメソッド その1(基本的なこと)

前置き

Ruby のクラスメソッドについてまとめます。 インスタンスメソッドと同じく、その1は、基本的なことを整理します。

書き方

インスタンスメソッドの定義と同じように、クラス定義内で、def の後にメソッド名を書きますが、メソッド名の前にself.をつけて定義します。

クラスメソッドのサンプル

以下、サンプルのクラスです。

class A 
    @@classval = "クラス変数 @@classval"
    @classinsval = "クラスインスタンス変数 @classinsval"

    # 通常のクラスメソッド
    def self.a_method
        @classinsval_a = "クラスインスタンス変数 @classinsval_a"
        p "called self.a_method."
    end

    # self の代わりにクラス名で定義もできる。
    def A.b_method
        @classinsval_b = "クラスインスタンス変数 @classinsval_b"
        p "called A.b_method."
    end

    # 変数表示用のクラスメソッド
    def self.show
        p "@@classinsval = #{@@classval}"
        p "@classinsval = #{@classinsval}"
        p "@classinsval_a = #{@classinsval_a}"
        p "@classinsval_b = #{@classinsval_b}"
    end

end

# クラス A を継承したクラス B (中身は空)
class B < A 
    
end      

irb でクラスを読み込んで、確認してみます。

まず、クラスのメソッドを確認してみます。

A.methods(false)      # => [:b_method, :a_method]         クラス A で定義されたメソッド
B.methods(false)      # => []          クラス B で定義されたメソッドは無い。
B.methods             # => [:b_method, :a_method, ・・・・・]       `false`を指定しなければ表示される。

A.a_method        # => "called self.a_method."
A.b_method        # => "called A.b_method."
B.a_method        # => "called self.a_method."
B.b_method        # => "called A.b_method."

selfを使っても、クラス名Aを使っても同じようにクラスメソッドが定義されています。クラス B でも、継承されたクラスメソッドがそのまま使用できます。

続けて、showメソッドで、変数を確認してみます。(クラス A, クラス B ともに a_method, b_method を実行した状態です。)

A.show
# => "@@classinsval = クラス変数 @@classval"
     "@classinsval = クラスインスタンス変数 @classinsval"
     "@classinsval_a = クラスインスタンス変数 @classinsval_a"
     "@classinsval_b = クラスインスタンス変数 @classinsval_b"

B.show
# => "@@classinsval = クラス変数 @@classval"
     "@classinsval = "
     "@classinsval_a = クラスインスタンス変数 @classinsval_a"
     "@classinsval_b = クラスインスタンス変数 @classinsval_b"

クラス B のクラスインスタンス変数のみ、値が入っていません。これは、クラスインスタンス変数が継承されないためで、代入(初期化)が実行されたクラスインスタンス変数のみ表示されます。

クラスメソッドから、インスタンス変数は参照できません。クラスメソッド内で@変数名と書くと、クラスインスタンス変数と解釈されるためです。

その1のまとめ

  • クラスメソッドは、クラス定義内で def self.[メソッド名] または def [クラス名].[メソッド名]で定義する。
  • クラスメソッドは、[クラス名].[メソッド名]で呼び出す。
  • クラスメソッド内では、クラス変数とクラスインスタンス変数は参照できる。インスタンス変数は参照できない。

その2に続きます。

【Ruby v2.6】クラスインスタンス変数

前置き

Ruby の「クラスインスタンス変数」についてまとめます。

クラスインスタンス変数とは

書き方

クラス定義内で、文字列の頭に「@」をつけて表します。インスタンスメソッド内で生成する「インスタンス変数」と同じ書き方なので注意が必要です。

生成

クラスインスタンス変数は、メソッド定義の外、もしくはクラスメソッド内で、@付きの文字列に何かオブジェクトを代入したタイミングで生成されます。

クラスインスタンス変数は、クラスオブジェクト毎に保持されます。インスタンス変数が「インスタンスオブジェクトの変数」であり、クラスインスタンス変数は「クラスオブジェクトの変数」ということです。

下記、サンプルクラスです。

class A
    # クラスインスタンス変数の生成
    @class_val_a = "@class_val_a"

    def self.set_b
        # クラスメソッド内でのクラスインスタンス変数の生成
        @class_val_b = "@class_val_b"
    end

    # クラスインスタンス変数の内容を変更(ここでは追加)
    def self.add_a
        @class_val_a << " add_a! "
    end
    
    def self.add_b
        @class_val_b << "  add_b! "
    end

    # クラスインスタンス変数の内容を表示
    def self.show
        p @class_val_a 
        p @class_val_b         
    end
end        

@class_val_a は、メソッド定義の外で代入(初期化)して生成しているクラスインスタンス変数です。@class_val_b は、クラスメソッド内で生成されます。他、それぞれのクラスインスタンス変数の値を書き換えるクラスメソッドと、値を参照するクラスメソッドです。

irb 等でクラスを読み込んで確認してみます。

A.instance_variables                 # => [:@class_val_a]
A.set_b                                      # @class_val_b を生成するクラスメソッド呼び出し
A.instance_variables                 # => [:@class_val_a, :@class_val_b] 

クラスインスタンス変数も、インスタンス変数と同様に、変数に値を代入したタイミングで生成されていることが分かります。
続けて、A のクラスを継承するクラスB を定義してみます。

class B < A            # A を継承しただけのクラス B の定義
end

B.instance_variables    # => []    クラスインスタンス変数がない! => クラスインスタンス変数は継承されない。

@class_val_bはともかく、@class_val_a もありません。これは、クラスインスタンス変数自体がクラスの継承の対象にならないことを表しています。

クラスメソッドは継承されているので、クラスB でset_bメソッドを呼び出し、@class_val_b を生成し、変数の値を変更してみます。

B.set_b
B.add_b

A.show          # => "@class_val_a"  "@class_val_b"
B.show          # => nil  "@class_val_b  add_b! "

クラスB で変更したクラスインスタンス変数の内容は、クラスA のクラスインスタンス変数に全く影響しておらず、それぞれ独立の変数になっていることが分かります。

どんなときに使ったらいいのか?

クラスから生成したインスタンスの統計的な情報(インスタンス何個作った、とか、インスタンス変数個々の合計値や平均値)を格納したり、インスタンスを生成せず、値を保持・管理するためのクラスを生成したい場合などに使うといいのかな、と思います。

まとめ

注意すること

  • クラスメソッド内で定義すると、生成のタイミングがずれ、nil 参照してしまう可能性があるので、特別な理由がない限り、メソッド定義の外で値を代入(初期化)することをお勧めします。
  • クラスインスタンス変数は継承されないので、継承したクラスでクラスインスタンス変数を使用する場合は、継承したクラスで代入(初期化)を行う必要があります。親クラスで生成したクラスインスタンス変数と同じ名前のクラスインスタンス変数を生成しても、親クラスのクラスインスタンス変数の値に影響はありません。
  • 生成されていないクラスインスタンス変数を参照すると、nil が返されます。
  • クラスインスタンス変数にインスタンスメソッドから参照したい場合は、クラスメソッドを介して参照する必要があります。

【Ruby v2.6】インスタンスメソッド その3(特異メソッド)

前置き

Rubyインスタンスメソッドについてまとめます。 その3は、特異メソッドについて。

特異メソッドとは

インスタンスは、クラスで定義された内容で生成されますが、「特異メソッド」は生成されたインスタンス自身に、新たに定義するインスタンスメソッドです。 インスタンスオブジェクトに直接定義するので、使用できるのは定義したインスタンスオブジェクトのみで、同じクラスでも違うインスタンスオブジェクトでは使用できません。

書き方

まずインスタンスオブジェクトを生成し、def [インスタンス].[メソッド名] という書き方で定義します。

サンプル

以下、サンプルのクラスで例を示します。

class A
    # 通常のインスタンスメソッド
    def a_public_method
        p "called a_method."
    end
end

まず、通常のクラス A の定義です。ここからインスタンスを生成して、特異メソッドを定義してみます。

obj1 = A.new        # => クラス A のインスタンス
obj2 = A.new        # => 同じクラスの別のインスタンス

def obj1.eigen_method                 # => obj1 に特異メソッドを定義する。
    p "called obj1.eigen_method"
end

この状態で、obj1obj2 について eigen_methodメソッドを呼び出してみると、obj1 だけが呼び出せることが分かります。

obj1.eigen_method            # => "called eigen_method."
obj2.eigen_method            # => NoMethodError 例外

特異メソッドの格納先「特異クラス」

通常のインスタンスメソッドは、インスタンスを定義したクラスに格納されていますが、特異メソッドの定義(格納先)は、インスタンスオブジェクト自身に格納されるわけではなく、「特異クラス」というクラスに格納されています。(「シングルトンクラス」とも呼ばれます)

インスタンスオブジェクトの特異クラスは、.singleton_classで参照できます。

obj1                    # => #<A:0x00000*******>     A のインスタンスオブジェクトを表す
obj1.singleton_class    # => #<Class:#<A:0x00000*******>>     「#<Class:」 は特異クラスを表す
obj1.singleton_class.superclass    # => A      obj1 の特異クラスは、A のサブクラスになっています。

クラス A で定義されているインスタンスメソッド、obj1 の特異クラスで定義されているインスタンスメソッドを表示すると、以下のようになります。

A.instance_methods(false)                 # => [:a_method]
obj1.singleton_class.instance_methods     # => [:eigen_method]
obj2.singleton_class.instance_methods     # => []

obj1 で同じように使用できるインスタンスメソッドですが、特異メソッドはクラス A ではなく、特異クラスに格納されています。また、obj2では特異メソッドを定義していないので、特異メソッドは空です。

「特異クラス」と「特異メソッド」の関係

図で表現すると、下記のようになります。

f:id:gyobo:20200812223318p:plain
特異クラス

インスタンスobj1に特異メソッドを追加すると、図のようにクラス A のサブクラスとして、obj1の特異クラスが生成され、特異メソッドの定義はそこに格納されます。¥

どんな場面で使用するか?*1

複数のインスタンスを生成していて、ある固有のインスタンスのみに必要な機能がどうしても出てきてしまった場合で、かつ、クラス定義を修正すると影響範囲が大きくなりすぎてしまうような場合に、応急処置的に定義して使用する使い方がほとんどかと思います。

基本的には、新規開発の設計段階などでは、しっかりとクラス定義を書くべきで、わざわざ「特異メソッドの利用を前提」として設計する必要は無いはずです。

その3のまとめ

  • インスタンスごとに、個別にインスタンスメソッドを定義することができ、「特異メソッド(singleton_method)」という。特異メソッドを使用できるのは、定義したインスタンスのみである。
  • 「特異メソッド」を定義すると、インスタンスを生成元のクラスのサブクラスとして「特異クラス」が生成され*2、「特異メソッド」は「特異クラス」に格納される。

*1:インスタンスオブジェクトの特異メソッドに限定して書きます。

*2:特異クラスが生成されるタイミングは、厳密には外からは分かりませんし、気にする必要はありません。実際、特異メソッドを作らずに obj1.singleton_class としても、特異クラス名を確認できます。

【Ruby v2.6】インスタンスメソッド その2(public, protected, private)

前置き

Rubyインスタンスメソッドについてまとめます。 その2は、public, protected, private について

書き方

クラス定義内で、デフォルトでインスタンスメソッドを定義すると、public のインスタンスメソッドとなります。 インスタンスメソッドの定義をprivate の後に記述すると private, protectedの後に記述すると protected のインスタンスメソッドになります。

public, protected, private の意味

Ruby での protected, private は、Javaなどのprotected, private とは少し動作が異なります。

public のインスタンスメソッド

制限なく呼び出せるメソッドです。[インスタンス].[メソッド名]で呼び出せます。

protected のインスタンスメソッド

そのクラスのインスタンスメソッドか、サブクラスで定義したインスタンスメソッド内であれば呼び出せるメソッドです。 一番分かりにくいので、サンプルで詳しく説明します。

private のインスタンスメソッド

レシーバーを付けて呼び出すことができないインスタンスメソッドです。[インスタンス].[メソッド名] では呼び出せません。self.[メソッド名]でもダメで、[メソッド名]だけで呼び出します。 この制限のため、同じクラスのインスタンスメソッド以外から呼び出しができないメソッドということになります。

サンプル

以下、サンプルのクラスです。

class A

    # デフォルトでは public
    def a_public_method
        p "called a_method."

        # protected メソッドの呼び出し
        self.a_protected_method
        
        # private メソッドの呼び出し(self等のレシーバーは付けられない。)
        a_private_method
    end

    protected       # ここ以降は protected のメソッドになる

    # protected メソッド
    def a_protected_method
        p "called a_protected_method."
    end

    private       # ここ以降は private のメソッドになる

    # private メソッド
    def a_private_method
        p "called a_private_method."
    end

end

# A のサブクラス B
class B < A 
    # A のインスタンスからprotectedメソッドを呼び出す
    def call_a_protected_method
        obj = A.new
        obj.a_protected_method
    end

    # A のインスタンスからprivateメソッドを呼び出す
    def call_a_private_method
        obj = A.new
        obj.a_private_method
    end
end

# A とは無関係のクラス C
class C
    # A のインスタンスからprotectedメソッドを呼び出す
    def call_a_protected_method
        obj = A.new
        obj.a_protected_method
    end

    # A のインスタンスからprivateメソッドを呼び出す
    def call_a_private_method
        obj = A.new
        obj.a_private_method
    end
end

irb でクラスを読み込んで、確認してみます。

クラス A のインスタンスを作成し、a_public_method を呼び出してみます。

obj_a = A.new
obj_a.a_public_method
# => "called a_method."
# => "called a_protected_method."
# => "called a_private_method."

obj_aインスタンスメソッドから、protected, private のメソッドが呼び出せています。しかし、インスタンスメソッドの外からprotected, private メソッドを呼び出そうとすると、NoMethodError例外が発生します。

obj_a.a_protected_method        # => NoMethodError 例外
obj_a.a_private_method          # => NoMethodError 例外

protectedprivate の差は、サブクラスや他のクラスのインスタンスメソッド内で使えるかどうか、になります。サンプルでは、クラス B は クラス A のサブクラス、クラス C はクラス A と継承関係のないクラスで、中身は全く同じです。インスタンスメソッド内でクラスAのインスタンスオブジェクトを生成して、protected, private メソッドへアクセスできるか確かめます。

obj_b = B.new
obj_b.call_a_protected_method      # => "called a_protected_method."
obj_b.call_a_private_method        # => NoMethodError 例外

obj_c = C.new
obj_c.call_a_protected_method      # => NoMethodError 例外
obj_c.call_a_private_method        # => NoMethodError 例外

クラス B のインスタンスメソッドからクラスA のインスタンス生成→protectedメソッド読み出しだけが呼び出せています。他はNoMethodError 例外で呼び出せません。

使い分け方

特に理由がなければ、デフォルトの public で問題ありません。インスタンス内部用に、関数的に使う場合や、外から実行しても意味が無い、都合が悪い場合は、private にして制限すると良いと思います。

使いどころが難しいのが protected です。実際、あまり使う機会がなさそう。 同じクラスのオブジェクト間で、相互にメソッドを実行する必要がある場合、他の無関係なクラスのインスタンスメソッドから実行されると困る、誤って実行されるのを防止する目的ならありそうですが、どういうシチュエーションか、あまり想像できないところです。。。

その2のまとめ

  • インスタンスメソッドは、public, protected, private の3種類ある。
  • 通常の定義では public となり、protectedの後に定義すると protected メソッド、private の後に定義すると private メソッドになる。
  • public なインスタンスメソッドは、制限なく呼び出しができる。
  • protected なインスタンスメソッドは、そのクラスのインスタンスメソッド、またはサブクラスのインスタンスメソッドから呼び出しができる。
  • private なインスタンスメソッドは、レシーバーを付けて呼び出しができない。このため、そのクラスのインスタンスメソッド以外からは呼び出せない。

その3に続きます。

【Ruby v2.6】インスタンスメソッド その1(基本的なこと)

前置き

Rubyインスタンスメソッドについてまとめます。 その1は、基本的なこと。

書き方

クラス定義内で、def の後にメソッド名を書き、機能の記述をendではさむことで定義できます。

インスタンスメソッドのサンプル

以下、サンプルのクラスです。

class A

    # アクセサの定義:getter, setter インスタンスメソッドを定義する。
    attr_accessor :instanceval_a

    @@classval_a = "クラス変数 @@classval_a"
    @classinstanceval_a = "クラスインスタンス変数 @classinstanceval_a"

    # 生成したオブジェクトを初期化するインスタンスメソッド initialize
    # クラスメソッド A.new から呼び出されるプライベートメソッド
    def initialize
        @instanceval_a = "インスタンス変数 @instanceval_a"

        p "called initialize."
    end

    # 通常のインスタンスメソッド a_method
    def a_method

        # 各変数へのアクセス確認
        p @@classval_a
        p @classinstanceval_a   # クラスインスタンス変数は参照できない。
        p @instanceval_a 

        "called a_method."
    end

end

irb でクラスを読み込んで、確認してみます。

まず、クラスのインスタンスメソッドを確認してみます。組み込みの.instance_methodsメソッドで確認できます。引数にfalseを指定すると、継承されたメソッド以外のものだけを表示できます。

A.instance_methods(false)      # => [:a_method, :instanceval_a=, :instanceval_a]

instanceval_a=instanceval_aは、attr_accessor で自動作成したsetter, getter メソッドです。 initialize メソッドは「プライベート」なメソッドなので、ここでは表示されません。

続いて、インスタンスを生成し、a_method を呼び出してみます。

obj = A.new      # => "called initialize."

obj.a_method
# =>
"クラス変数 @@classval_a"
nil
"インスタンス変数 @instanceval_a"
=> "called a_method."

インスタンスメソッド内からは、クラス変数、インスタンス変数は参照できますが、クラスインスタンス変数は参照できないことが分かります(nil が返ってくる)。これは、クラスインスタンス変数は、「クラスオブジェクト自身が持つインスタンス変数」であって、「クラスのインスタンスオブジェクトが持つインスタンス変数」ではないからです。

その1のまとめ

その2に続きます。

【Ruby v2.6】インスタンス変数

前置き

Ruby の「インスタンス変数」についてまとめます。

インスタンス変数とは

書き方

クラス定義内で、文字列の頭に「@」をつけて表します。「クラスインスタンス変数」と同じ書き方なので注意が必要です。

生成

インスタンス変数は、インスタンスメソッド内で@付きの文字列に、何かオブジェクトを代入したタイミングで生成されます。 他の場所(メソッドの外や、クラスメソッド内)で @付きの文字列に代入した場合は、「クラスインスタンス変数」と解釈されます。

インスタンス変数は、インスタンス毎に保持されます。このため、同じクラスから別々のインスタンスを生成しても、インスタンス変数は共有されません。

下記、サンプルクラスです。

class A

    # attr_accessor は、ゲッター、セッターのメソッドのみを生成する。
    # なので、この宣言だけではインスタンス変数はまだ生成されません。
    attr_accessor :insval_c

    # インスタンス初期化用 インスタンスメソッド
    def initialize
        # このメソッドは、インスタンスを生成したタイミングで実行されるので、
        # インスタンス生成と同時にインスタンス変数も生成されます。
        @insval_a = "@insval_a"
    end

    # 通常のインスタンスメソッド
    def a_method
        # このインスタンス変数は、a_method メソッドを実行したら生成されます。
        @insval_b = "@insval_b"
    end
    
end

@insval_aは、インスタンスメソッド initialize で代入されています。initialize メソッドは、インスタンス生成時に実行されるので、 インスタンス生成と同じタイミングで生成されます。

@insval_bは、別のメソッド a_method 内で代入されているので、インスタンス生成時にはまだ生成されていません。a_methodメソッドが実行されたタイミングで、初めて生成されます。

@insval_cは、attr_accessor として定義されていますが、attr_accessorは、ゲッターメソッド、セッターメソッドを定義するだけなので、実行はされません。ですので、この宣言だけでは@insval_cは生成されず、セッターで値を代入しないと生成されません。

irb 等でクラスを読み込んで確認すると、下記のようになります。

obj1 = A.new                  # インスタンス obj1 を生成
obj1.instance_variables       # => [:@insval_a]
obj1.a_method                 # a_method メソッドを実行すると・・・
obj1.instance_variables       # => [:@insval_a, :@insval_b]
obj1.insval_c                 # => nil
obj1.insval_c = "@insval_c"   # insval_c は値をセットして始めて生成される
obj1.instance_variables       # => [:@insval_a, :@insval_b, :@insval_c]

obj2 = A.new                  # 別のインスタンス obj2 を生成
obj2.instance_variables       # => [:@insval_a]      # obj1 の変数とは共有されない 

まとめ

注意すること

  • 特別な理由がない限り、インスタンス変数は initializeメソッドなど、必ず実行されるインスタンスメソッド内で値を代入(初期化)することをお勧めします。
  • 生成されていないインスタンス変数を参照すると、nil が返されます。
  • インスタンスオブジェクトが持つ変数なので、同じインスタンスインスタンスメソッドからしか参照できません。クラス A のインスタンスだとしても、クラス A のクラスメソッドからは参照できません。(同じクラス定義内にあるので、勘違いすることがあるかもしれません。)