30 Jun 2010

Rubyシングルトンパターンいろいろ + Railsでの使用例

Rubyでのシングルトンの定義はいろいろあります。カウントアップを実現する例をいくつかの方法で書いてみました。


この記事を書くにあたって以下の記事を参考にしました。
Singleton Classes in Ruby - RUBY EYE FOR JAVA GUY
ここで書かれているRubyのシングルトンクラスを定義するためのいくつかの前提知識は割愛しました。理解が甘いなと感じた場合は読んでみてください。


シングルトンいろいろ



シングルトンメソッドを利用する方法



クラス変数/メソッドを利用する方法



  1. class MyCount  
  2.   @@count = 0;  
  3.   
  4.   def self.up  
  5.     @@count += 1  
  6.   end  
  7.   
  8. end  
  9.   
  10. puts MyCount.up #=> 1  
  11. puts MyCount.up #=> 2  

シンプルにクラスに対してシングルトンメソッドを定義しています。別の言い方をするとClassクラスのインスタンスであるMyCountにシングルトンメソッドを定義しています。


クラスへの仕様追加は以下の方法でもできます。ただし、def MyCount.upからMyCountクラスのクラス変数を参照することができないのでMyCount.up内で定義しています。
  1. class MyCount; end  
  2.   
  3. def MyCount.up  
  4.   @@count ||= 0  
  5.   @@count +=1  
  6. end  




シングルトンクラスを利用する方法



暗示的にシングルトンクラスを生成する



  1. my_count = Object.new  
  2. def my_count.up  
  3.   @@count ||=0   
  4.   @@count += 1  
  5. end  
  6.   
  7. puts my_count.up  
  8. puts my_count.up  


この例と先に挙げたdef self.upの例との違いは何でしょうか?self.upはクラスにupというシングルトンメソッドを定義しています。それに対してmy_count.upの場合、upがmy_countに対して定義されたとき新しく無名クラスがそのオブジェクトとクラスの間に挿入します。その無名クラスをシングルトンクラスと呼びます。これはあるオブジェクトに対して最初にメソッドが定義された時に起こります。

my_count.upを呼ぶとき、インタープリタは定義を見つけるために最初に無名クラスを検索し、実装のためにクラスハイラーキーを探します。



- 内部クラス
上記の例で起きていることがイメージできましたでしょうか?次の例を見るとシングルトンクラスがどこにいるか分かりやすくなると思います。

こちらの例は機械猫さんのところから拝借しました。
[ruby][デザパタ]Rubyでデザパタ Singleton編 - 機械猫の日記

  1. class Object  
  2.   class MyCount  
  3.     @@count=0  
  4.     def up  
  5.       @@count+=1  
  6.     end  
  7.   end  
  8.   
  9.   @@ins = MyCount.new  
  10.     
  11.   def count  
  12.     @@ins  
  13.   end  
  14. end  
  15.   
  16. puts count.up  
  17. puts count.up  

Objectクラスに内部クラスMyCountが定義されています。@@insはObjectクラスのクラス変数なのでObjectクラス中で一意になります。MyCountが無名である場合を想像してください。厳密には違う場所に定義されます。シングルトンクラスはほぼ内部クラスと似たような振る舞いをしますが、変数のスコープなどに違いがあります。



明示的にシングルトンクラスを生成する



  1. my_count = Object.new  
  2.   
  3. class << my_count #=> シングルトンクラスを定義して、my_countのインスタンスに仕様を追加  
  4.   @@count = 0  
  5.   def up  
  6.     @@count +=1  
  7.   end  
  8. end  
  9.   
  10. puts my_count.up  
  11. puts my_count.up  

class << my_countはmy_countとObjectの間のシングルトンクラスに明示的にアクセスできます。この場合upはシングルトンクラスに対するインスタンスメソッドとなり、my_countに対するシングルトンメソッドとなります。別の言い方をするとシングルトンクラスはシングルトンメソッドを格納するための入れ物になります。この記法はいくつかのシングルトンメソッドをまとめて定義するのに便利です。


インスタンスではなくクラス定義にシングルトンクラスを定義する場合
  1. class MyCount  
  2.   class << self  
  3.     @@count = 0  
  4.     def up  
  5.       @@count += 1  
  6.     end  
  7.   end  
  8. end  
  9.   
  10. puts MyCount.up  
  11. puts MyCount.up  

class << selfはself=MyCountに対してシングルトンクラスを定義し、仕様を追加しています。インスタンスにシングルトンクラスを定義する先ほどの例との違いはありません。なぜならRubyでは全てのクラスがオブジェクトだからです。Railsでは明示的にシングルトンクラスをクラス定義内に追加するこの方法が好まれているらしいです。



Singletonモジュールを利用する方法



Singletonモジュールを使うとincludeしたクラス自体をシングルトンクラスにしてくれます。シングルトンメソッドで使用するインスタンス変数はinitializeに書けば参照することができます。newはprivateにされているためインスタンスは生成できません。ClassName.instanceでインスタンスを取得します。
  1. require 'singleton'  
  2.   
  3. class MyCount  
  4.  include Singleton  
  5.  def up  
  6.   @count+=1  
  7.  end  
  8.  def initialize  
  9.   @count=0  
  10.  end  
  11. end  
  12.   
  13. a,b = MyCount.instance, MyCount.instance   
  14. puts a == b # => true  
  15. # a.new # NoMethodError - new is private …  


Singletonモジュールが何をしているのかはRuby で Singleton - うっかりプログラミング日誌Singleton - apidock.comを参考にされるとよく理解が進むと思います。



シングルトンクラスを定義したクラスを継承した場合



  1. class MyCount  
  2.   class << self  
  3.     @@count = 0  
  4.     def up  
  5.       @@count += 1  
  6.     end  
  7.   end  
  8. end  
  9.   
  10. MyCount.up  #=> 1  
  11.   
  12. class Guests < MyCount; end  
  13.   
  14. 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
  1. ActiveSupport::Inflector.inflections do |inflect|  
  2.   inflect.plural /^(ox)$/i, '\1en'  
  3.   # inflect.singular /^(ox)en/i, '\1'  
  4.   # inflect.irregular 'person', 'people'  
  5.   # inflect.uncountable %w( fish sheep )  
  6. end  

inflectionsというメソッドを屈折規則を定義したブロック付きで呼び出しています。


以下が、簡略化したActiveSupport::Inflectorの定義です。
  1. module ActiveSupport  
  2.   module Inflector  
  3.     class Inflections  
  4.       def self.instance  
  5.         @__instance__ ||= new  
  6.       end  
  7.   
  8.       attr_reader :plurals:singulars:uncountables:humans  
  9.   
  10.       def initialize  
  11.         @plurals@singulars@uncountables@humans = [], [], [], []  
  12.       end  
  13.   
  14.       def plural(rule, replacement)  
  15.         @uncountables.delete(rule) if rule.is_a?(String)  
  16.         @uncountables.delete(replacement)  
  17.         @plurals.insert(0, [rule, replacement])  
  18.       end  
  19.   
  20.       def singular(rule, replacement); end  
  21.       def irregular(singular, plural); end  
  22.       def uncountable(*words); end  
  23.       def human(rule, replacement); end  
  24.       def clear(scope = :all); end  
  25.     end  
  26.   
  27.     # Yields a singleton instance of Inflector::Inflections   
  28.     # so you can specify additional inflector rules.  
  29.     def inflections  
  30.       if block_given?  
  31.         yield Inflections.instance  
  32.       else  
  33.         Inflections.instance  
  34.       end  
  35.     end  
  36.   
  37.     def pluralize(word)  
  38.       result = word.to_s.dup  
  39.   
  40.       if word.empty? || inflections.uncountables.include?(result.downcase)  
  41.         result  
  42.       else  
  43.         inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }  
  44.         result  
  45.       end  
  46.     end  
  47.   
  48.     def singularize(word); end  
  49.     def humanize(lower_case_and_underscored_word); end  
  50.     def titleize(word); end  
  51.     def tableize(class_name); end  
  52.     def classify(table_name); end  
  53.   end  
  54. 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のインスタンスを取得したいとき"の二通りの使われ方をしています。どこからも単/複数形の規則を追加できる、その定義が全ての呼び出しに反映されるというのがシングルトンを使う利点です。こんな風に適所、シングルトンパターンを使うタイミングを思いつくでしょうか?この”どこからも”というのがシングルトンパターンを使うべき時の"匂い"になるでしょう。


.