ruby - создать синглтон с параметрами? - PullRequest
17 голосов
/ 10 марта 2011

Я видел, как определить класс как синглтон ( как создать синглтон в ruby ​​):

require 'singleton'

class Example
  include Singleton
end

Но что, если я хочу дать ему немногопараметры для этого единственного экземпляра, то есть, пример должен всегда иметь определенные инициализированные свойства.Например, скажем, у меня был класс, единственная цель которого - войти в файл (это всего лишь пример), но для его входа в систему требуется имя файла.

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

Какя могу сделать MyLogger одиночным, но убедиться, что он получает имя_файла?

Ответы [ 5 ]

14 голосов
/ 01 августа 2011

Вот еще один способ сделать это - поместить имя файла журнала в переменную класса:

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 еще не установлен.

4 голосов
/ 10 марта 2011

Singleton не предоставляет эту функциональность, но вместо использования 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> или хотя бы при первом вызове, если вы знаете, что это будет первый раз.

3 голосов
/ 11 августа 2018

Вот подход, который я использовал для решения аналогичной проблемы, которым я хотел бы поделиться, если вы или другие люди сочтете его подходящим:

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, который можно использовать для создания дополнительных экземпляров для тестирования или будущих альтернативных конфигураций, при этом определяя MyLogger какединичный экземпляр этого стандарта соответствует стандартному шаблону Ruby Singleton.Вы можете разделить методы экземпляра между ними по своему усмотрению.

Ruby's Singleton автоматически создает экземпляр при первой необходимости, поэтому параметры Logger#initialize должны быть доступны по требованию в MyLogger.new, но вы можетеконечно, извлекайте значения из среды или устанавливайте их как MyLogger переменные экземпляра класса во время конфигурации до того, как экземпляр singleton будет когда-либо использоваться, что согласуется с тем, что экземпляр singleton фактически является глобальным.

1 голос
/ 10 марта 2011

Это было слишком долго, чтобы поместить в комментарий (например, 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, как только экземпляр готов.

Он также делает метод «новый» класса закрытым после создания первого экземпляра.

0 голосов
/ 25 июля 2016

Простой синглтон, который не зависит от 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...