ruby - 用参数创建单例?

时间:2011-03-10 12:08:54

标签: ruby singleton

我已经看到如何将一个类定义为一个单例(how to create a singleton in ruby):

require 'singleton'

class Example
  include Singleton
end

但是,如果我想为该单个实例提供一些参数,这意味着,示例应始终具有初始化的某些属性。例如,假设我有一个类,其唯一目的是登录文件(这只是一个示例),但它需要一个文件的名称才能在它可以工作之前登录。

class MyLogger
  def initialize(file_name)
    @file_name = file_name
  end
end

如何让MyLogger成为单身,但要确保它获得了file_name?

5 个答案:

答案 0 :(得分:14)

这是另一种方法 - 将日志文件名放在类变量中:

require 'singleton'
class MyLogger
  include Singleton
  @@file_name = ""
  def self.file_name= fn
    @@file_name = fn
  end
  def initialize
    @file_name = @@file_name
  end
end

现在你可以这样使用它:

MyLogger.file_name = "path/to/log/file"
log = MyLogger.instance  # => #<MyLogger:0x000.... @file_name="path/to/log/file">

instance的后续调用将返回路径名未更改的同一对象,即使您稍后更改了类变量的值也是如此。一个很好的进一步触摸是使用另一个类变量来跟踪是否已经创建了一个实例,并且在这种情况下让file_name=方法引发异常。如果尚未设置initialize,您还可以@@file_name提出异常。

答案 1 :(得分:4)

Singleton不提供此功能,但您可以自己编写

,而不是使用单例
class MyLogger
  @@singleton__instance__ = nil
  @@singleton__mutex__ = Mutex.new
  def self.instance file_name
    return @@singleton__instance__ if @@singleton__instance__
    @@singleton__mutex__.synchronize {
      return @@singleton__instance__ if @@singleton__instance__
      @@singleton__instance__ = new(file_name)
    }
    @@singleton__instance__
  end
  private
  def initialize file_name
    @file_name = file_name
  end
  private_class_method :new
end

它应该可以工作,但我没有测试代码。

此代码强制您使用MyLogger.instance <file_name>或至少在第一次通话时使用,如果您知道它将是第一次通话。

答案 2 :(得分:2)

这是我用来解决类似问题的一种方法,如果您或其他人认为合适,我想与您分享:

require 'singleton'

class Logger
  attr_reader :file_name

  def initialize file_name
    @file_name = file_name
  end
end


class MyLogger < Logger
  include Singleton

  def self.new
    super "path/to/file.log"
  end

  # You want to make {.new} private to maintain the {Singleton} approach;
  # otherwise other instances of {MyLogger} can be easily constructed.
  private_class_method :new
end

p MyLogger.instance.file_name
# => "path/to/file.log"

MyLogger.new "some/other/path"
# => ...private method `new' called for MyLogger:Class (NoMethodError)

我已经测试了2.32.42.5中的代码;早期版本当然可能表现出不同的行为。

这使您可以拥有一个通用的参数化Logger类,该类可用于创建其他实例以进行测试或将来的替代配置,同时按照Ruby的标准将MyLogger定义为其单个实例Singleton模式。您可以根据需要在它们之间拆分实例方法。

Ruby的Singleton会在首次需要时自动构造实例,因此Logger#initialize参数必须在MyLogger.new中按需提供,但是您当然可以从环境中获取值,或者在使用单例实例之前的配置过程中,将它们设置为MyLogger类实例变量,这与单例实例有效地全局一致。

答案 3 :(得分:1)

这太长了,无法发表评论(例如,stackoverflow说它太长了)

好的,这就是我想出的:

class MyLogger
  @@singleton__instance__ = nil
  @@singleton__mutex__ = Mutex.new
  def self.config_instance file_name
    return @@singleton__instance__ if @@singleton__instance__
    @@singleton__mutex__.synchronize {
      return @@singleton__instance__ if @@singleton__instance__
      @@singleton__instance__ = new(file_name)
      def self.instance
        @@singleton__instance__
      end
      private_class_method :new
    }
    @@singleton__instance__
  end
  def self.instance
    raise "must call MyLogger.config_instance at least once"
  end
  private
  def initialize file_name
    @file_name = file_name
  end
end

这使用'config_instance'来创建和配置单例实例。一旦实例准备好,它就重新定义了self.instance方法。

在创建第一个实例后,它还会将'new'类方法设为私有。

答案 4 :(得分:0)

不依赖于Singleton模块

的简单单例
class MyLogger
  def self.instance(filepath = File.join('some', 'default', 'path'))
    @@instance ||= new(filepath).send(:configure)
  end

  def initialize(filepath)
    @filepath = filepath
  end
  private_class_method :new

  def info(msg)
    puts msg
  end

  private

  def configure
    # do stuff
    self
  end
end

使用示例

logger_a = MyLogger.instance
# => #<MyLogger:0x007f8ec4833060 @filepath="some/default/path">

logger_b = MyLogger.instance
# => #<MyLogger:0x007f8ec4833060 @filepath="some/default/path">

logger_a.info logger_a.object_id
# 70125579507760
# => nil

logger_b.info logger_b.object_id
# 70125579507760
# => nil

logger_c = MyLogger.new('file/path')
# NoMethodError: private method `new' called for MyLogger:Class