ぎょーぼのぶろぐ

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

【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 としても、特異クラス名を確認できます。