Edited at

Rubyデザインパターン 9日目 : Singleton

More than 3 years have passed since last update.

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)

スクリーンショット 2015-07-27 11.25.28.png


 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つおさらい

1. オブジェクト(インスタンス)が一つしか存在しない

2. そのオブジェクト(インスタンス)へのアクセスはどこからでも行える(※ただし、グローバル変数のような使い方は絶対にしないこと)

実例では、WEBrick、Rake、Railsでよく使われているそうです。ソースコードを漁ると出会うことができるでしょう。


参照

http://www.nulab.co.jp/designPatterns/designPatterns2/designPatterns2-1.html