1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

Rubyのデザインパターン。プログラミングにおけるシングルトンパターンの活用

2022-01-31 09:24:42

はじめに
      シングルトンパターンは、デザインパターンの中でも最もシンプルな形の一つである。このパターンの目的は、あるクラスのオブジェクトを、システム内でそのクラスの唯一のインスタンスにすることです。これを実現するためには、まずクライアント側でインスタンス化することから始めます。そのため、オブジェクトクラスのユニークなインスタンスのみを生成できるような仕組みで、生成したいオブジェクトへのアクセスをすべて"block"する必要がある。インスタンス生成処理を制限するために、ファクトリーメソッドを使用する。このメソッドは静的メソッド(クラスメソッド)であるべきです。なぜなら、クラスのインスタンスが別の一意のインスタンスを生成することは意味がないからです。


キーポイント
      1つは、クラスは1つのインスタンスしか持てないこと、2つは、そのインスタンスを自分で生成しなければならないこと、3つは、そのインスタンスをシステム全体に提供しなければならないことです。
      具体的な実装としては、1、シングルトンパターンのクラスはプライベートコンストラクタのみを提供する、2、クラス定義にそのクラスの静的プライベートオブジェクトを含む、3、そのクラスが


シングルトン

classClassVariableTester 
 @@class_count = 0 
 
 def initialize 
  @instance_count = 0 
 end 
 
 def increment 
  @@class_count = @@class_count + 1 
  @instance_count = @instance_count + 1 
 end 
 
 def to_s 
  "class count :#{@@class_count} -- instance count :#{@instance_count}" 
 end 
  
end 
 
cv1 = ClassVariableTester.new 
cv1.increment 
cv1.increment 
puts("cv1:#{cv1}") 
cv2 = ClassVariableTester.new 
puts("cv2:#{cv2}") 
 
#cv1:class count :2 -- instance count :2 
#cv2:class count :2 -- instance count :0 



2つ目のオブジェクトが生成されたとき、@@class_countは2、ii@instance_countは0ですが、クラス変数はすべてのインスタンスで共有されているので、@@class_countが2になってからパーティcv1.includeが2回呼ばれます。 2つ目のClassVariableTesterオブジェクトcv2が作られたとき、@@class_countは共有されていたのでこの時点でも@@class_countが2になっています。
また、インスタンス変数は現在のオブジェクトにしか使えないので、インスタンスオブジェクト cv2 の @@instance_count は 0 になっています。 
このクラス変数の特徴は、シングルインスタンスパターン  

class SimpleLogger 
 
 @@instance = SimpleLogger.new 
  
 def self.get_instance 
  @@instance 
 end 
 
 private_class_method :new 
end 
 
sl1 = SimpleLogger.get_instance 
sl2 = SimpleLogger.get_instance 
puts sl1 == sl2 



結果は: true 。
クラス変数はクラスのインスタンスを1つだけ保持するために使用され、クラスメソッドはこの1つのインスタンスを返すために必要です。  
しかし、別のインスタンスオブジェクトを SimpleLogger.new で作成することは可能なので、new メソッドは private にする必要があります。  

sl3 = SimpleLogger.new 
 private method `new' called for SimpleLogger:Class (NoMethodError) 
 
require 'singleton' 
class SimpleLogger 
 include Singleton 
 
end 
 
#puts SimpleLogger.new 
sl1 = SimpleLogger.instance 
sl2 = SimpleLogger.instance 
puts sl1 == sl2 



結果は、「真」です。 
シングルトンは、Rubyのクラスライブラリで提供されており、シングルトン・クラスの作成を簡素化することができます。 
シングルトンに混ぜることで、クラス変数の作成、シングルトンインスタンスの初期化、クラスレベルのインスタンスメソッドの作成、new privateの作成が省かれます。 
SimpleLogger.instanceでロガーのインスタンスを1つ取得します。 
しかし、この2つのアプローチには違いがあります。 
1つ目のアプローチは、「イーガー・インスタンス」と呼ばれています。 
インスタンスオブジェクトは、実際に必要となる前に作成されます。 
2つ目の方法は、「遅延インスタンス化(lazy instantiation)」と呼ばれます。 
インスタンスが呼び出されたときに作成されます。
しかし、このSingletonは実際には何も防ぐことができません。public_class_methodを使って、新しいメソッドをパブリックなものに変更するために使用することができます。 
クラスを開き、新しいメソッドをpublicに設定したら、SimpleLogger.newを使ってオブジェクトを作成します。  

class SimpleLogger 
 public_class_method :new 
end 
 
puts SimpleLogger.new 


さらに2つのケースで。

(i) グローバル変数の使用、グローバル変数はプログラムと密結合しているため、グローバル変数を使用しないようにする。 
実は、シングルトンパターンもグローバル変数も同じように機能するのです。 
$logger = SimpleLogger.new 

(ii) クラスを1つのインスタンスとして使用することで 

class SimpleLogger 
  
 WARNING = 1 
 INFO = 2 
 
 def initialize(file) 
  @@log = File.open(file, "w") 
  @@level = WARNING 
 end 
  
 
 def self.warning(msg) 
  puts @@level > WARNING 
  @@log.puts(msg) if @@level > WARNING 
  @@log.flush 
 end 
 
 def self.level 
  @@level 
 end 
 
 def self.level = (new_level) 
  @@level = new_level 
 end 
  
end 
SimpleLogger.new("test.txt") 
puts SimpleLogger.level 
SimpleLogger.level = SimpleLogger::INFO 
puts SimpleLogger.level 
SimpleLogger.warning("warning") 



インスタンス

require 'rubygems'
require 'watir'
require 'singleton'
class AutoTest
 include Singleton
 def OpenUrl(url)
  @browser= Watir::Browser.new
  @browser.goto(url)
  @url=url
 end
 def set_textarea(text)
  @browser.text_field(:id,'kw').set(text)
 end
 def click
  @browser.button(:id,'su').click
 end
end
test,test2 = AutoTest.instance
test.OpenUrl('http://www.baidu.com')
test.set_textarea('aslandhu')
test.click



ここでは、AutoTest のインスタンスが 2 つ作成されますが、2 つ目のインスタンスは実際には nil であり、これは正常に作成されなかったことを意味します。

require 'rubygems'
require 'watir'
require 'singleton'
require 'thread'
class TestOneObj
 
end
class <<TestOneObj
 include Singleton
 def instance
  @browser= Watir::Browser.new
  self
 end
 def openurl(url)
  @browser.goto(url)
 end
 def set_textarea(text)
  @browser.text_field(:id,'kw').set(text)
 end
  def click
  @browser.button(:id,'su').click
  end
end
test = TestOneObj.instance
test2 = TestOneObj.instance
p test.inspect
p test2.inspect
test.openurl('www.baidu.com')
test2.set_textarea('aslandhu')
test.click


上記のコードでは、2つのBrowserオブジェクトを作成しようとしていますが、実際には作成されるオブジェクトは両方とも同じものです。IE のウィンドウが 2 つ開かれていますが、オブジェクトは 1 つです。つまり、test と test2 は同じオブジェクトです。