RubyでSingletonパターンを実装する

このまえ、友人とデザインパターンの話をしてたような気がするので書いてみた。

SingletonはGOFのデザインパターンの一つです。
使い所は、システム内で絶対に一つにしておかないといけないもの仕組みのものに使います。
例えば、アプリの設定とか見た目(Look&Feel)とかシステムの何かを管理するだとかに使ったりします。

Singletonは以下の条件を満たす必要があります。

  • 作成したクラスのインスタンスは一つだけであることが保証されている
  • コード上のどこからでも、生成した1つだけのインスタンスにアクセスできる

Javaで上の条件を満たすものはこんな感じ

1
2
3
4
5
6
7
8
9
10
11
final class Singleton {
    private static Singleton instance;
    private Singleton(){};

    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

これと同じ実装をRubyでやるとこんな感じになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Singleton
  @@singleton_obj = nil
  @@count = 0

  # 以下の行をコメントアウトすると
  # オブジェクトが再生成されるので
  # シングルトンにならない
  private_class_method:new

  def initialize
    @@count = 1
  end
  
  def self.get_instance
    if @@singleton_obj == nil
      @@singleton_obj = new
    end
    @@singleton_obj
  end

  def self.count_up
    @@count+=1
  end

  def self.print_count
    p @@count
  end
  
end

p '-----------------------'
p '初回インスタンス取得'
p Singleton.get_instance
Singleton.print_count
p '-----------------------'
p '2回目のインスタンス取得でオブジェクトに変更がない'
p Singleton.get_instance
Singleton.count_up
Singleton.print_count
p '-----------------------'
p 'newするとエラーが飛ぶ'
begin
  Singleton.new
rescue
  p 'newでエラーが飛んだ'
end

結構長いですねw

上記のコードの実行結果は、以下になります。
オブジェクトに変化が無いことがわかりますね。
つまり、オブジェクトが1つであることが、保証されています。

1
2
3
4
5
6
7
8
9
10
11
"-----------------------"
"初回インスタンス取得"
#<Singleton:0x007f91519026f0>
1
"-----------------------"
"2回目のインスタンス取得でオブジェクトに変更がない"
#<Singleton:0x007f91519026f0>
2
"-----------------------"
"newするとエラーが飛ぶ"
"newでエラーが飛んだ"

自力でSingletonを実装すると上記のようなコードなのですが、
実は、Rubyにはsingletonをやってくれる便利なモジュールがあります。
これを使うと、Mix-inしたクラスのインスタンスは常に同じものを返してくれます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
require 'singleton'

class MySingleton

  include Singleton

  @@count = 0

  def initialize
    @@count = 1
  end

  def self.count_up
    @@count+=1
  end

  def self.print_count
    p @@count
  end
end

p '-----------------------'
p '初回インスタンス取得'
p MySingleton.instance
MySingleton.print_count
p '-----------------------'
p '2回目のインスタンス取得でオブジェクトに変更がない'
p MySingleton.instance
MySingleton.count_up
MySingleton.print_count
p '-----------------------'
p 'newするとエラーが飛ぶ'
begin
  MySingleton.new
rescue
  p 'newでエラーが飛んだ'
end

実行結果は、こんな感じ

1
2
3
4
5
6
7
8
9
10
11
"-----------------------"
"初回インスタンス取得"
#<MySingleton:0x007fd2fa8c9658>
1
"-----------------------"
"2回目のインスタンス取得でオブジェクトに変更がない"
#<MySingleton:0x007fd2fa8c9658>
2
"-----------------------"
"newするとエラーが飛ぶ"
"newでエラーが飛んだ"

モジュールを使うと、スッキリとしたコードになりますね。
当たり前ですが、newしようとするとエラーになります。