発端は、「特異クラスから元のインスタンスを取り出せるか( Object#singleton_class
の逆ができるか)」と疑問に思ったこと。 #instance
みたいなメソッドで取り出せるなら楽だと思ったが、それは正に Singleton
モジュールがやっていることである。特異クラスはsingletonパターンに当てはまりそうなのに、 Singleton
モジュールとは関係が無いのはなぜなのだろう?
※本記事は疑問点を書いているだけであり、自己解決はしていない。
基本知識
singletonパターン
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
クラスという形で統合する
# 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