30 Jun 2010

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

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


この記事を書くにあたって以下の記事を参考にしました。
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のインスタンスを取得したいとき"の二通りの使われ方をしています。どこからも単/複数形の規則を追加できる、その定義が全ての呼び出しに反映されるというのがシングルトンを使う利点です。こんな風に適所、シングルトンパターンを使うタイミングを思いつくでしょうか?この”どこからも”というのがシングルトンパターンを使うべき時の"匂い"になるでしょう。


.