我已经看到如何将一个类定义为一个单例(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?
答案 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.3
,2.4
和2.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