LoginSignup
17
19

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-08-05

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でよく使われているそうです。ソースコードを漁ると出会うことができるでしょう。

参照

17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19