Ruby の Prime がつよい
いろいろあって Prime クラスのソースを読んでいたらとても勉強になったよ。
Prime
- 素数全体を表すクラスだよ
- 素数全体というオブジェクトは1つしか存在しえないので、シングルトンになるよ
- 利便性のため、デフォルトインスタンスのメソッドをクラスメソッドとしても使用できるよ
- Prime.new で Ruby 1.8 の Prime の互換クラスのインスタンスを生成できるよ
ソースをよむ
lib/prime.rb から必要部分を抜粋するよ。
require "singleton" require "forwardable" class Prime include Enumerable @the_instance = Prime.new # obsolete. Use +Prime+::+instance+ or class methods of +Prime+. def initialize @generator = EratosthenesGenerator.new extend OldCompatibility warn "Prime::new is obsolete. use Prime::instance or class methods of Prime." end class << self extend Forwardable include Enumerable # Returns the default instance of Prime. def instance; @the_instance end def method_added(method) # :nodoc: (class<< self;self;end).def_delegator :instance, method end end module OldCompatibility end end
シングルトンパターン
さらに抜粋していくよ。
class Prime @the_instance = Prime.new class << self # Returns the default instance of Prime. def instance; @the_instance end end end
Singleton モジュールは使わずにシングルトンパターンを実現しているよ。どうしてだろう。
旧 Prime 互換クラスのインスタンス
class Prime @the_instance = Prime.new # obsolete. Use +Prime+::+instance+ or class methods of +Prime+. def initialize @generator = EratosthenesGenerator.new extend OldCompatibility warn "Prime::new is obsolete. use Prime::instance or class methods of Prime." end module OldCompatibility end end
OldCompatibility モジュールには旧 Prime 互換のインスタンスメソッドが宣言されているよ(中略しているよ)。 これを extend することによって、Prime.new で生成したインスタンスに特異メソッドを定義しているんだね。 互換が不要になったら OldCompatibility モジュールを削除するだけで済むからつよいよ。
Singleton モジュールを include すると Prime.new がプライベートクラスメソッドになってしまうから、旧 Prime 互換クラスのインスタンスを生成するためには Singleton モジュール を使わずにシングルトンパターンを実現する必要があったんだね。
ちなみに、デフォルトインスタンスを生成した時点では Prime.initialize は定義されていないから、このとき生成したインスタンスには OldCompatibility モジュールは extend されないよ。すごいね。
デフォルトインスタンスのメソッドをクラスメソッドとして使用する
class Prime class << self extend Forwardable def method_added(method) # :nodoc: (class<< self;self;end).def_delegator :instance, method end end end
Fowardable モジュールを extend しているね。 Fowardable モジュールはクラスに対してメソッドの委譲機能を定義するモジュール。 この場合は、method_added の引数 method を instance に委譲するよ。
(class<<self;self;end) の戻り値は Prime の特異クラス。 ここでの self は Prime だから、Prime のクラスメソッドを定義するためには (class<<self;self;end) をレシーバにする必要があるんだね(class << self の中にまた class << self が出てくるからわかりづらいよね)。
Module#method_added はクラスやモジュールにインスタンスメソッドが定義されたときに呼び出されるメソッドだよ。 Prime にインスタンスメソッドが追加されると method_added が呼ばれて同名のクラスメソッドを定義、処理をデフォルトインスタンスに委譲することによって、デフォルトインスタンスのメソッドをクラスメソッドとして使用することができるんだね。
まとめ
よくわからない文体で書くのやめよう。