Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第9日目はSingletonです。(http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=morizyun00-22)
9日目Singleton
オブジェクト生成は一般的に負荷のかかる作業です。
生成するオブジェクト数を制限したり、ひとつに限定したいときなどに使うパターンがSingletonです。
シングルトンクラスのオブジェクト生成を、シングルトンクラス自身が提供することになります。
Singleton サンプルコード
class SimpleLogger
attr_accessor :level
ERROR = 1
WARNING = 2
INFO = 3
def initialize
@log = File.open("log.txt", "w")
@level = WARNING
end
def error(msg)
@log.puts(msg)
@log.flush
end
def warning(msg)
@log.puts(msg) if @level >= WARNING
@log.flush
end
def info(msg)
@log.puts(msg) if @level >= INFO
@log.flush
end
end
logger = SimpleLogger.new
logger.level = SimpleLogger::INFO # このインスタンスを持ち回りながら使う
logger.info('1番目の処理を実行')
logger.info('2番目の処理を実行')
この状態で常に同じオブジェクトをプログラム内で扱いたいとき、いろんなところでlogger
オブジェクトを使い回さないといけません。
そして、SimpleLogger.new
をするたびに異なったオブジェクトができてしまいます。
この問題点を解決するために、これからシングルトンを作ってみましょう。
class SimpleLogger
#
#
#
@@instance = SimpleLogger.new
def self.instance
return @@instance
end
end
logger1 = SimpleLogger.instance
logger2 = SimpleLogger.instance
logger3 = SimpleLogger.new
logger1.object_id # => 70354806787840 same
logger2.object_id # => 70354806787840 same
logger3.object_id # => 70354806787560 other
@@instance
にはすでにSimpleLogger
インスタンスが作成されて入っています。
こういった実装にすることで、.instance
メソッドからインスタンスを呼び出す限り、必ず同じオブジェクトを参照できることになります。
この状態では、まだ完全にシングルトンとは呼べません。
なぜなら、まだnew
メソッドによりオブジェクトを複数作れてしまうからです。
class SimpleLogger
@@instance = SimpleLogger.new
def self.instance
return @@instance
end
private_class_method :new
end
SimpleLogger.new # => error
これでシングルトンは完成となります。
new
メソッドはもはやSimpleLogger
クラス内でしか使用できなくなりました。
Let's Ruby!!!
require 'singleton'
class SimpleLogger
include Singleton # クラス変数を定義して、シングルトンインスタンスで初期化して、self.instanceというクラスレベルメソッドを作りnewメソッドをプライベート化してくれる
#
#
#
end
SimpleLogger.new # => private method `new' called for SimpleLogger:Class (NoMethodError)
SimpleLogger.instance # => #<SimpleLogger:0x007f97b9a7e510>
なんということでしょう。
include Singleton
をするだけで、Singletonを作ることができます。
このモジュール型では遅延型インスタンス化を実現しています。
積極的・遅延型インスタンス化とは
- 積極的インスタンス生成 : 実際に必要になる前にインスタンスを生成する(クラス変数型
- 遅延型インスタンス生成 : 必要になった時にインスタンスを生成する(モジュール型
先ほどのモジュール型だと、.instance
メソッドが呼ばれた時にインスタンスを生成するので後者です。
どちらがいいかは時と場合によるでしょう。
## シングルトンとしてのモジュール
module ModuleBasedLogger
ERROR = 1
WARNING = 2
INFO = 3
@@log = File.open('log.txt', 'w')
@@level = WARNING
def self.error(msg)
@@log.puts(msg)
@@log.flush
end
def self.warning(msg)
@@log.puts(msg) if @@level >= WARNING
@@log.flush
end
def self.info(msg)
@@log.puts(msg) if @@level >= INFO
@@log.flush
end
def self.level=(new_level)
@@level = new_level
end
end
ModuleBasedLogger.level = 3
ModuleBasedLogger.info('お知らせがあります〜〜〜')
こういった形で、シングルトン機能を実現できます。
モジュールベースで作ることで、「これはメソッドの入れ物として作っていて、インスタンス化するために作ったオブジェクトではない」と明示的にできる意図があります。
まとめ
Singletonは諸刃の剣のようなものに例えられます。
メリットは、ただ一つだけの何かが存在するということを保証してくれます。これは、シングルトンが一つのオブジェクト(インスタンス)しか作れないようにしてくれているからです。
▼シングルトンの大きな特徴2つおさらい
- オブジェクト(インスタンス)が一つしか存在しない
- そのオブジェクト(インスタンス)へのアクセスはどこからでも行える(※ただし、グローバル変数のような使い方は絶対にしないこと)
実例では、WEBrick、Rake、Railsでよく使われているそうです。ソースコードを漁ると出会うことができるでしょう。
参照