Одиночка — это порождающий паттерн, который гарантирует существование только одного объекта определённого класса, а также позволяет достучаться до этого объекта из любого места программы.
Одиночка имеет такие же преимущества и недостатки, что и глобальные переменные. Его невероятно удобно использовать, но он нарушает модульность вашего кода.
Вы не сможете просто взять и использовать класс, зависящий от одиночки в другой программе. Для этого придётся эмулировать присутствие одиночки и там. Чаще всего эта проблема проявляется при написании юнит-тестов.
Применимость: Многие программисты считают Одиночку антипаттерном, поэтому его всё реже и реже можно встретить в Ruby-коде.
Признаки применения паттерна: Одиночку можно определить по статическому создающему методу, который возвращает один и тот же объект.
Наивный Одиночка (небезопасный в многопоточной среде)
Топорно реализовать Одиночку очень просто — достаточно скрыть конструктор и предоставить статический создающий метод.
Тот же класс ведёт себя неправильно в многопоточной среде. Несколько потоков могут одновременно вызвать метод получения Одиночки и создать сразу несколько экземпляров объекта.
main.rb: Пример структуры паттерна
# Класс Одиночка предоставляет метод instance, который позволяет клиентам
# получить доступ к уникальному экземпляру одиночки.
class Singleton
@instance = new
private_class_method :new
# Статический метод, управляющий доступом к экземпляру одиночки.
#
# Эта реализация позволяет вам расширять класс Одиночки, сохраняя повсюду
# только один экземпляр каждого подкласса.
def self.instance
@instance
end
# Наконец, любой одиночка должен содержать некоторую бизнес-логику, которая
# может быть выполнена на его экземпляре.
def some_business_logic
# ...
end
end
# Клиентский код.
s1 = Singleton.instance
s2 = Singleton.instance
if s1.equal?(s2)
print 'Singleton works, both variables contain the same instance.'
else
print 'Singleton failed, variables contain different instances.'
end
output.txt: Результат выполнения
Singleton works, both variables contain the same instance.
Многопоточный Одиночка
Чтобы исправить проблему, требуется синхронизировать потоки при создании объекта-Одиночки.
main.rb: Пример структуры паттерна
# Класс Одиночка предоставляет метод instance, который позволяет клиентам
# получить доступ к уникальному экземпляру одиночки.
class Singleton
attr_reader :value
@instance_mutex = Mutex.new
private_class_method :new
def initialize(value)
@value = value
end
# Статический метод, управляющий доступом к экземпляру одиночки.
#
# Эта реализация позволяет вам расширять класс Одиночки, сохраняя повсюду
# только один экземпляр каждого подкласса.
def self.instance(value)
return @instance if @instance
@instance_mutex.synchronize do
@instance ||= new(value)
end
@instance
end
# Наконец, любой одиночка должен содержать некоторую бизнес-логику, которая
# может быть выполнена на его экземпляре.
def some_business_logic
# ...
end
end
# @param [String] value
def test_singleton(value)
singleton = Singleton.instance(value)
puts singleton.value
end
# Клиентский код.
puts "If you see the same value, then singleton was reused (yay!)\n"\
"If you see different values, then 2 singletons were created (booo!!)\n\n"\
"RESULT:\n\n"
process1 = Thread.new { test_singleton('FOO') }
process2 = Thread.new { test_singleton('BAR') }
process1.join
process2.join
output.txt: Результат выполнения
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)
RESULT:
FOO
FOO