Как создать синглтон в Ruby? Лично мне приходят на ум 4 способа.

Примечание: это почти полный копипаст моего поста на Хабре

Способ первый: include Singleton

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

  • Делает .new приватным
  • Добавляет .instance, который создает и/или возвращает экземпляр
  • Переопределяет #dup и #clone, чтобы те вызывали ошибку

Полагаю, это классическая реализация синглтона. Во всяком случае, я бы писал что-то подобное, будь я Java-программистом. Ничего особенного, все работает.

Способ второй: модуль с module_function

У Module есть метод #module_function, который позволяет использовать определяемые методы “на себе”. Такой подход используется, например, в Math. Пример:

module M
  module_function

  def f
    :f
  end
end

M.f # ==> :f

Я бы не рекомендовал такую реализацию синглтона по нескольким причинам:

  • Это все же остается миксином, который можно включить в другой класс/модуль. Это, конечно, не страшно, но что-то здесь не так.
  • Сделать приватные методы можно только с помощью костылей, т.к. module_function создает публичную копию метода в самом себе. Я придумал такой:
    module M
      module_function
    
      def f
        g
      end
    
      def g
        'hello'
      end
    
      singleton_class.send(:private, :g)
    end
    
    M.f # ==> 'hello'
    M.g # ==> NoMethodError
    
  • (Личная причина) Модули, использующие module_function по моему мнению должны быть сборником stateless методов, помогающих в чем-либо. Соответственно, include MyModule будет использоваться только для того, чтобы сделать методы доступными в текущем модуле без обращения к MyModule. Такой сценарий использования приводится в “The Ruby Programming Language” с Math

Кстати, можно для этих же целей использовать extend self вместо module_function. Это избавит от проблемы приватных методов. Но, допустим, небезызвестный ruby-style-guide не одобряет такой подход (ссылка: https://github.com/bbatsov/ruby-style-guide#module-function)

Думаю, очевидно, что extend self работает иначе, но я не уверен, что есть какая-то опасная разница.

Способ третий: класс/модуль только с методами класса

class MyClass
  def self.f
    :f
  end

  def self.g
    :g
  end
end

MyClass.f # ==> :f

или так:

class MyClass
  class << self
    def f
      :f
    end

    private

    def g
      :g
    end
  end
end

MyClass.f # ==> :f
MyClass.g # ==> NoMethodError

Разумеется, вместо class можно использовать module. В вышеупомянутом стайл-гайде такой подход не рекомендуется. Вместо него рекомендуют module_function.

В моей практике такой подход встречался чаще всего. Лично мне он всегда казался каким-то страшным костылем, но при этом он мне нравится больше использования Singleton, т.к. MySingleton.do_something для меня выглядит привлекательнее MySingleton.instance.do_something.

Создать экземпляр Object

В последнее время я постоянно использую такой подход:

MySingleton = Object.new
class << MySingleton
  def f
    g
  end

  private

  def g
    puts 'hello'
  end
end

Теперь наш синглтон - это просто экземпляр Object с нужными нам методами:

MySingleton.class # ==> Object

Вот только и здесь есть проблемы:

  • Мы можем использовать #clone/#dup. Решение: переопределить их, как это сделано в Singleton
  • При инспектировании объекта мы получаем что-то вроде #<Object: ...>. Решение: переопределить методы #to_s и #inspect. Кстати, ruby-style-guide рекомендует делать это на всех “собственных” (локальных? Не могу подобрать слово) классах. Ссылка: https://github.com/bbatsov/ruby-style-guide#define-to-s
  • Здесь пишут, что у такого подхода есть проблемы с генерацией документации. Не могу подтвердить или опровергнуть, т.к. не использую генераторы. Ссылка: https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along