Как создать синглтон в 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 работает иначе, но я не уверен, что есть
какая-то опасная разница.
upd. все-таки не очень очевидно. extend self заставляет модуль добавить самого себя в список подключенных модулей (не знаю, как это проще написать), а module_function создает копии методов. Если конкретно, то посмотрите на код:
module M module_function def f :f end end class A include M end M.f # ==> :f # module_function делает методы приватными в include A.new.send(:f) # ==> :f module M def f :TROLOLO end end A.new.send(:f) # ==> :TROLOLO M.f # ==> :f
Способ третий: класс/модуль только с методами класса
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
Небольшое отступление: class << self
Думаю, все видели синтаксис:
class MyClass class << self def do_something_on_class ... end end def do_something_on_instance ... end end
При этом я неоднократно замечал, что человек не знает, что означает эта
конструкция. Собственно, в Ruby у объектов на самом деле есть два класса: тот,
чьим экзем��ляром он является, и т.н. "singleton class" — класс синглтона.
Наверняка вы видели примеры, где мы определяем методы прямо на объектах.
Как-то так:
x = Object.new def x.hey 'hey' end x.hey # ==> 'hey'
В класс-ориентированном ООП у объекта нет своих методов. Поведение объекта
определяется классом, которому он принадлежит. Поэтому мы не можем определить
метод на объекте с помощью def x.hey, мы должны определить его в классе. Вот
только если мы сделаем это, то тогда все экземпляры Object должны будут
получить метод #hey, чего мы не хотим. Поэтому Ruby создает "дополнительный"
класс у объекта, называемый класс синглтона. Получить его можно с помощью метода
#singleton_class. В общем, я увлекся и, наверное, только запутал тех, кто не
знал о "singleton class". Это очень интересная сторона Ruby, поэтому предлагаю
прочитать о ней самостоятельно.
Собственно, если коротко, то конструкция class << some_object "входит" в класс
сингтона. Сравните:
class A # enter class A scope def hey 'hey' end end class << A # enter class A singleton class scope def hey 'hello' end end A.new.hey # ==> 'hey' A.hey # ==> 'hello'
