プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめています。
今回は、シングルトンパターンについてまとめました。
デザインパターン記事一覧
[【Rubyによるデザインパターン】テンプレートメソッドパターンのメモ - Qiita] (https://qiita.com/yuki_0920/items/07382034dfb65f0a13a0)
【Rubyによるデザインパターン】ファクトリーメソッドパターンのメモ - Qiita
【Rubyによるデザインパターン】ストラテジーパターンのメモ - Qiita
【Rubyによるデザインパターン】コマンドパターンのメモ - Qiita
【Rubyによるデザインパターン】オブザーバーパターンのメモ - Qiita
【Rubyによるデザインパターン】シングルトンパターンのメモ - Qiita <- 本記事
シングルトンパターンについて
たった1つのリソースをアプリケーション内で共有する場合に有用なパターンです。
1つのリソースを表現するクラスをシングルトンクラスとして定義し、シングルトンクラスのインスタンスは1つだけしか存在しないように制御します。
サンプルコード
趣味の筋トレにちなんで、ジムにおけるトレーニングマシーンの管理をテーマとします。
トレーニングマシーンはとても高価でジムに1台しか存在しないため、あるトレーニーがトレーニングマシーンを使用している間は、別のトレーニーはトレーニングマシーンを使用できません。
この仕様をプログラムで実装します。
シングルトンパターン適用前からです。
class TrainingMachine
attr_accessor :available
def initialize
@available = true
end
end
class Trainee
def initialize(name, machine)
@name = name
@machine = machine
end
def start_training
if @machine.available
@machine.available = false
puts "#{@name}はトレーニングを始めます"
else
puts 'トレーニングマシーンは使用中なのでトレーニングを開始できません'
end
end
def finish_training
@machine.available = true
puts "#{@name}はトレーニングを終了します"
end
end
実行時はこのとおりです。
machine = TrainingMachine.new
trainee1 = Trainee.new('トレーニー1', machine)
trainee1.start_training
trainee2 = Trainee.new('トレーニー2', machine)
trainee2.start_training
trainee1.finish_training
trainee2.start_training
trainee2.finish_training
# トレーニー1はトレーニングを始めます
# トレーニングマシーンは使用中なのでトレーニングを開始できません
# トレーニー1はトレーニングを終了します
# トレーニー2はトレーニングを始めます
# トレーニー2はトレーニングを終了します
実行時に、 machine に TrainingMachine のインスタンスを代入しています。
start_training メソッドでは、 machine の @available
の値によってトレーニングマシーンが利用可能かどうかを判定していますが、machine にアクセスするために、 Trainee には初期化時に machine を引数として渡す必要があります。
この machine の受け渡しは、トレーニーの増加やトレーニングマシーンの種類の増加が起こるたびに、たちまち複雑化してしまいます。
シングルトンパターンを適用して書き換えた場合です。
Ruby のライブラリ Singleton というモジュールを利用します。
require 'singleton'
class TrainingMachine
include Singleton
attr_accessor :available
def initialize
@available = true
end
end
class Trainee
def initialize(name)
@name = name
# トレーニングマシーンの唯一のインスタンスを呼び出す
@machine = TrainingMachine.instance
end
def start_training
if @machine.available
@machine.available = false
puts "#{@name}はトレーニングを始めます"
else
puts 'トレーニングマシーンは使用中なのでトレーニングを開始できません'
end
end
def finish_training
@machine.available = true
puts "#{@name}はトレーニングを終了します"
end
end
実行結果はこのとおりです。
trainee1 = Trainee.new('トレーニー1')
trainee1.start_training
trainee2 = Trainee.new('トレーニー2')
trainee2.start_training
trainee1.finish_training
trainee2.start_training
trainee2.finish_training
# トレーニー1はトレーニングを始めます
# トレーニングマシーンは使用中なのでトレーニングを開始できません
# トレーニー1はトレーニングを終了します
# トレーニー2はトレーニングを始めます
# トレーニー2はトレーニングを終了します
Trainee の初期化時には、 TrainingMachine のインスタンスを外部から引数として受け渡さなくてよくなり、呼び出し元では、 TrainingMachine を意識しなくて良くなりました。
変わりに Trainee 初期化時には @machineに TrainingMachine.instance で唯一のインスタンスをセットしています。
シングルトンクラスのインスタンスへのアクセス
ドキュメントによると、
Singleton を Mix-in したクラスのクラスメソッド instance はその唯一のインスタンスを返します。
とのことなので、 instance メソッドによってアクセス可能です。のリソースの
ちなみに、 new メソッドによってインスタンスを生成した場合は例外が発生します。
Singleton モジュールによって new メソッドがプライベートメソッドとして定義されていることが原因です。
TrainingMachine.new
# private method `new' called for TrainingMachine:Class (NoMethodError)
おわりに
シングルパターンは、外部のシステムと作用し合うケースで利用することが多いパターンです。
『Rubyによるデザインパターン』には、シングルトンパターンの使用上の注意点や Active Support での実例も紹介されていましたので、より詳しく知りたい方はご参照ください。