LoginSignup
9
5

More than 5 years have passed since last update.

Rubyの特異クラス(singleton class)はsingletonパターンと無関係なものなのか?

Last updated at Posted at 2019-04-12

発端は、「特異クラスから元のインスタンスを取り出せるか( Object#singleton_class の逆ができるか)」と疑問に思ったこと。 #instance みたいなメソッドで取り出せるなら楽だと思ったが、それは正に Singleton モジュールがやっていることである。特異クラスはsingletonパターンに当てはまりそうなのに、 Singleton モジュールとは関係が無いのはなぜなのだろう?

※本記事は疑問点を書いているだけであり、自己解決はしていない。

基本知識

singletonパターン

Singleton パターン - Wikipedia

Singleton パターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つである。GoF (Gang of Four; 4人のギャングたち) によって定義された。Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。


Rubyにおいては、クラスに Singleton モジュールをmix-inするだけで作れる。標準ライブラリでの一例は Prime クラス

require 'prime'

Prime.ancestors
#=> [Prime, Singleton, Enumerable, Object, Kernel, BasicObject]

obj = Prime.new
#=> NoMethodError: private method `new' called for Prime:Class
obj1 = Prime.instance   #=> #<Prime:0xZZZZ>   # 新規作成
obj2 = Prime.instance   #=> #<Prime:0xZZZZ>
obj1.equal? obj2   #=> true

特異クラス

Rubyは全てのデータがオブジェクト、つまり何らかのクラスのインスタンスだが、加えて各オブジェクトは自分専用のクラスにも属する。(※例外は後述)

obj1 = Object.new               #=> #<Object:0xXXXX>
obj2 = Object.new               #=> #<Object:0xYYYY>
IClass = obj1.class             #=> Object
SClass = obj1.singleton_class   #=> #<Class:#<Object:0xXXXX>>

class IClass   # class Object と同じ
    def foo
        p "Hello, an instance method!"
    end
end

class SClass   # class << obj1 と同じ
    def bar
        p "Hello, a singleton method!"
    end
end

obj1.foo   #=> "Hello, an instance method!"
obj2.foo   #=> "Hello, an instance method!"
obj1.bar   #=> "Hello, a singleton method!"
obj2.bar   #=> NoMethodError: undefined method `bar' for #<Object:0xYYYY>

obj1 の特異クラス(定数 SClass に割り当てたもの)はインスタンスとして obj1 のみを持つため、ここに追加したインスタンスメソッドは obj2 など他のオブジェクトに対しては呼び出せない。

特異クラスの取得は上記のように Object#singleton_class を使う。また、あるクラスが特異クラスかどうか調べるには、 Module#singleton_class? を使う。

IClass.singleton_class?   #=> false
SClass.singleton_class?   #=> true

例外1

nil, true, false の特異クラス(を取得しようとして返るクラス)は通常のクラス、つまりそれぞれ NilClass, TrueClass, FalseClass である。

nil.singleton_class   #=> NilClass

例外2

Integer, Float, Symbol クラスのインスタンスはどの特異クラスにも属さない。

123.singleton_class   #=> TypeError: can't define singleton

疑問

上の例を見る限り、特異クラスはsingletonパターンの振る舞いをしているように見える。しかし実際は両者に隔たりがあり、できないことが存在する。

obj1 = Object.new               #=> #<Object:0xXXXX>
SClass = obj1.singleton_class   #=> #<Class:#<Object:0xXXXX>>

# Singletonモジュールをmix-inしていないことが確認できる
SClass.ancestors   #=> [#<Class:#<Object:0xXXXX>>, Object, Kernel, BasicObject]

# 元のインスタンスを取得できない
SClass.instance   #=> NoMethodError: undefined method `instance' for #<Class:#<Object:0xXXXX>>
SClass.new        #=> TypeError: can't create instance of singleton class

# 例外1のオブジェクト(nilなど)は特異クラスを取得しても特異クラスが返らない
nil.singleton_class.singleton_class?   #=> false
nil.singleton_class.equal? nil.class   #=> true

# 逆に、Singletonモジュールをmix-inしたクラスのインスタンスは、特異クラスを取得すると元のクラスと異なる(これは当たり前?)
require 'prime'
Prime.singleton_class?    #=> false
s_inst = Prime.instance   #=> #<Prime:0xZZZZ>
s_inst.singleton_class    #=> #<Class:#<Prime:0xZZZZ>>
s_inst.singleton_class.equal? s_inst.class   #=> false

恐らく歴史的には「特異クラス」という考え方ができたのがsingletonパターンと無関係なのだろうが、関係を作ってもいいように思う。以下のようになっていると何か不都合があるのだろうか?

  • 特異クラスが Singleton モジュールをmix-inする(またはそれに相当する動作をする)
    • #instance メソッドで元のオブジェクトを取得できる
  • Singleton モジュールをmix-inしたクラスが特異クラスとして元のクラスを返す
    • つまり #singleton_class メソッドが #class メソッドのエイリアスになる
    • クラスに対する #singleton_class? メソッドも true を返す
  • 例外1のオブジェクトのクラス( NilClass など)はこれらを体現したものとなる
  • いっそのこと SingletonClass クラスという形で統合する
SingletonClassクラスの想像
# Classクラスの子クラスである
SingletonClass.ancestors
#=> [SingletonClass, Class, Module, Object, Kernel, BasicObject]

# 特異クラスを表す
Object.new.singleton_class   #=> #<SingletonClass:#<Object:0xAAAA>>
NilClass.class               #=> SingletonClass

# 唯一のインスタンスを返すメソッドを持つ
NilClass.instance   #=> nil

# singletonパターンのクラスはこのクラスのインスタンスとして作る
Prime = SingletonClass.new do
    # include Singleton は不要
end
Prime.instance   #=> #<Prime:0xPPPP>

おまけ:発端の疑問の解決策

「特異クラスから元のインスタンスを取り出せる」ようにする方法は、以下のページで質問・回答されている。C拡張ライブラリを作る方法が最もきれいに思える。

Retrieve a Ruby object from its singleton class? - Stack Overflow

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5