この記事を書くにあたって以下の記事を参考にしました。
Singleton Classes in Ruby - RUBY EYE FOR JAVA GUY
ここで書かれているRubyのシングルトンクラスを定義するためのいくつかの前提知識は割愛しました。理解が甘いなと感じた場合は読んでみてください。
シングルトンいろいろ
シングルトンメソッドを利用する方法
クラス変数/メソッドを利用する方法
- class MyCount
- @@count = 0;
- def self.up
- @@count += 1
- end
- end
- puts MyCount.up #=> 1
- puts MyCount.up #=> 2
シンプルにクラスに対してシングルトンメソッドを定義しています。別の言い方をするとClassクラスのインスタンスであるMyCountにシングルトンメソッドを定義しています。
クラスへの仕様追加は以下の方法でもできます。ただし、def MyCount.upからMyCountクラスのクラス変数を参照することができないのでMyCount.up内で定義しています。
- class MyCount; end
- def MyCount.up
- @@count ||= 0
- @@count +=1
- end
シングルトンクラスを利用する方法
暗示的にシングルトンクラスを生成する
- my_count = Object.new
- def my_count.up
- @@count ||=0
- @@count += 1
- end
- puts my_count.up
- puts my_count.up
この例と先に挙げたdef self.upの例との違いは何でしょうか?self.upはクラスにupというシングルトンメソッドを定義しています。それに対してmy_count.upの場合、upがmy_countに対して定義されたとき新しく無名クラスがそのオブジェクトとクラスの間に挿入します。その無名クラスをシングルトンクラスと呼びます。これはあるオブジェクトに対して最初にメソッドが定義された時に起こります。
my_count.upを呼ぶとき、インタープリタは定義を見つけるために最初に無名クラスを検索し、実装のためにクラスハイラーキーを探します。
- 内部クラス
上記の例で起きていることがイメージできましたでしょうか?次の例を見るとシングルトンクラスがどこにいるか分かりやすくなると思います。
こちらの例は機械猫さんのところから拝借しました。
[ruby][デザパタ]Rubyでデザパタ Singleton編 - 機械猫の日記
- class Object
- class MyCount
- @@count=0
- def up
- @@count+=1
- end
- end
- @@ins = MyCount.new
- def count
- @@ins
- end
- end
- puts count.up
- puts count.up
Objectクラスに内部クラスMyCountが定義されています。@@insはObjectクラスのクラス変数なのでObjectクラス中で一意になります。MyCountが無名である場合を想像してください。厳密には違う場所に定義されます。シングルトンクラスはほぼ内部クラスと似たような振る舞いをしますが、変数のスコープなどに違いがあります。
明示的にシングルトンクラスを生成する
- my_count = Object.new
- class << my_count #=> シングルトンクラスを定義して、my_countのインスタンスに仕様を追加
- @@count = 0
- def up
- @@count +=1
- end
- end
- puts my_count.up
- puts my_count.up
class << my_countはmy_countとObjectの間のシングルトンクラスに明示的にアクセスできます。この場合upはシングルトンクラスに対するインスタンスメソッドとなり、my_countに対するシングルトンメソッドとなります。別の言い方をするとシングルトンクラスはシングルトンメソッドを格納するための入れ物になります。この記法はいくつかのシングルトンメソッドをまとめて定義するのに便利です。
インスタンスではなくクラス定義にシングルトンクラスを定義する場合
- class MyCount
- class << self
- @@count = 0
- def up
- @@count += 1
- end
- end
- end
- puts MyCount.up
- puts MyCount.up
class << selfはself=MyCountに対してシングルトンクラスを定義し、仕様を追加しています。インスタンスにシングルトンクラスを定義する先ほどの例との違いはありません。なぜならRubyでは全てのクラスがオブジェクトだからです。Railsでは明示的にシングルトンクラスをクラス定義内に追加するこの方法が好まれているらしいです。
Singletonモジュールを利用する方法
Singletonモジュールを使うとincludeしたクラス自体をシングルトンクラスにしてくれます。シングルトンメソッドで使用するインスタンス変数はinitializeに書けば参照することができます。newはprivateにされているためインスタンスは生成できません。ClassName.instanceでインスタンスを取得します。
- require 'singleton'
- class MyCount
- include Singleton
- def up
- @count+=1
- end
- def initialize
- @count=0
- end
- end
- a,b = MyCount.instance, MyCount.instance
- puts a == b # => true
- # a.new # NoMethodError - new is private …
Singletonモジュールが何をしているのかはRuby で Singleton - うっかりプログラミング日誌やSingleton - apidock.comを参考にされるとよく理解が進むと思います。
シングルトンクラスを定義したクラスを継承した場合
- class MyCount
- class << self
- @@count = 0
- def up
- @@count += 1
- end
- end
- end
- MyCount.up #=> 1
- class Guests < MyCount; end
- Guests.up #=> 2
継承したクラスGuestsのupはMyCountで定義されたシングルトンクラスのupを参照するみたいです。シングルトンクラスはそのまま子クラスに渡されるんですね。
Railsでのシングルトンパターンの例
こちらを参考にしました。
Ruby Singleton Pattern Again - Dalibor Nasevic
シングルトンパターンのRubyでの良い例の一つは、RailsでのInflections クラスの実装でしょう。Railsの異なる場所で使用されている全ての屈折規則(inflection rule - 単語の変換を定義する規則)にグローバルアクセスを与えているのはシングルトンインスタンス(Inflections.instance)です。
まずは呼び出し元のinflectルールの定義から見ていきましょう。
$RAILS_ROOT/config/initializers/inflections.rb
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.plural /^(ox)$/i, '\1en'
- # inflect.singular /^(ox)en/i, '\1'
- # inflect.irregular 'person', 'people'
- # inflect.uncountable %w( fish sheep )
- end
inflectionsというメソッドを屈折規則を定義したブロック付きで呼び出しています。
以下が、簡略化したActiveSupport::Inflectorの定義です。
- module ActiveSupport
- module Inflector
- class Inflections
- def self.instance
- @__instance__ ||= new
- end
- attr_reader :plurals, :singulars, :uncountables, :humans
- def initialize
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
- end
- def plural(rule, replacement)
- @uncountables.delete(rule) if rule.is_a?(String)
- @uncountables.delete(replacement)
- @plurals.insert(0, [rule, replacement])
- end
- def singular(rule, replacement); end
- def irregular(singular, plural); end
- def uncountable(*words); end
- def human(rule, replacement); end
- def clear(scope = :all); end
- end
- # Yields a singleton instance of Inflector::Inflections
- # so you can specify additional inflector rules.
- def inflections
- if block_given?
- yield Inflections.instance
- else
- Inflections.instance
- end
- end
- def pluralize(word)
- result = word.to_s.dup
- if word.empty? || inflections.uncountables.include?(result.downcase)
- result
- else
- inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
- result
- end
- end
- def singularize(word); end
- def humanize(lower_case_and_underscored_word); end
- def titleize(word); end
- def tableize(class_name); end
- def classify(table_name); end
- end
- end
ActiveSupport::Inflector.inflectionsは29行目のinflectionsメソッドを呼び出します。ブロックが定義されている場合は、シングルトンインスタンスであるInflections.instanceを引数にブロックを実行します。先ほどのブロック内をもう一度見てください。inflectを引数に取っています。Inflections.instanceが代入され、inflect.plural /^(ox)$/i, '\1en'が実行されます。
inflectはInflector::Inflectionsのインスタンスです。Inflector::Inflectionsはインスタンス変数に屈折規則を格納した配列を保持しています。14行目のpluralメソッドを参照してみましょう。Inflector::Inflectionsのインスタンス変数@pluralsに新しい屈折規則が追加されます。
実際の変換はActiveSupport::Inflector.pluralizeで行います。43行目の行頭ではinflectionsを引数なしで呼び出しており、33行目のとおりInflector.instanceを返します。gsub!はマッチが無い場合nilを返します。マッチする規則が発見された場合は結果を返しbreakしています。
Inflector.inflectionsメソッドは"単/複数規則を定義するとき"、"Inflector::Inflectionsのインスタンスを取得したいとき"の二通りの使われ方をしています。どこからも単/複数形の規則を追加できる、その定義が全ての呼び出しに反映されるというのがシングルトンを使う利点です。こんな風に適所、シングルトンパターンを使うタイミングを思いつくでしょうか?この”どこからも”というのがシングルトンパターンを使うべき時の"匂い"になるでしょう。
.