【问题标题】:ruby - create singleton with parameters?ruby - 使用参数创建单例?
【发布时间】:2019-11-03 21:53:04
【问题描述】:

我已经了解了如何将一个类定义为单例 (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 成为单例但确保它获得文件名?

【问题讨论】:

  • 如果要传入参数,确定要使用单例吗?
  • 是的。我相信在某些情况下单例是有意义的,但它应该具有一些初始配置。

标签: ruby singleton


【解决方案1】:

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

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= 方法引发异常。如果尚未设置 @@file_name,您还可以让 initialize 引发异常。

【讨论】:

  • 如果@@file_name 是单例,为什么还要费心将@@file_name 复制到@file_name 而不是只使用@@file_name?
【解决方案2】:

单例不提供此功能,但您可以自己编写而不是使用单例

class MyLogger
  @@singleton__instance__ = nil
  @@singleton__mutex__    = Mutex.new

  def self.instance(file_name)
    return @@singleton__instance__ if @@singleton__instance__

    @@singleton__mutex__.synchronize do
      return @@singleton__instance__ if @@singleton__instance__

      @@singleton__instance__ = new(file_name)
    end
    @@singleton__instance__
  end

  private

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

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

此代码强制您使用 MyLogger.instance &lt;file_name&gt; 或至少在第一次调用时使用,如果您知道它将是第一次调用。

【讨论】:

  • 所以我想我必须在创建 @@__singleton_instance__ 之后覆盖 self.new 否则你仍然可以做 MyLogger.new
  • 好的,这就是我想出的:
  • 在方法self.instance 中使用splat 运算符保持更通用的设置*params,如(def self.instance *params),并设置new 调用,使用splat 运算符也@987654329 @.
【解决方案3】:

这是我用来解决类似问题的一种方法,我想分享一下,以防你或其他人觉得它合适:

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 类,它可用于创建额外的实例以用于测试或未来的替代配置,同时将 MyLogger 定义为它的单个实例,遵循 Ruby 的标准化 Singleton 模式.您可以根据需要在它们之间拆分实例方法。

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

【讨论】:

    【解决方案4】:

    这太长了,不能放在评论中(例如 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 方法。

    它还会在创建第一个实例后将“新”类方法设为私有。

    【讨论】:

    • 在我的回答中已修复,您可以在类级别设置 privet new,对于 def self.instance,new 仍然可见
    【解决方案5】:

    不依赖于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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-22
      • 2014-09-03
      • 1970-01-01
      相关资源
      最近更新 更多