Новичкам на заметку: реализация шаблона Singleton в Ruby

Задумывались ли вы когда-нибудь, занимаясь объектно-ориентированным программированием, почему всегда нужно создавать экземпляр класса? В ряде случаев достаточно только одного экземпляра, который предусматривает возможность глобального использования после инициализации. 

Некоторые программисты объявляют экземпляр класса, присваивают его глобальной переменной при запуске проекта и применяют его на протяжении всей разработки. А есть ли вариант получше глобальной переменной? Да, классы Singleton

Singleton (в переводе на русский язык  —  “одиночка”) представляет собой порождающий шаблон проектирования. Он изначально был одним из шаблонов, описанных так называемой “бандой четырех”, куда входили авторы: Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес.

Понятие шаблона проектирования Singleton

В своей книге “Приёмы объектно-ориентированного проектирования. Паттерны проектирования” “банда четырех” описывает 23 шаблона, которые подразделяются на 3 категории: порождающие, поведенческие и структурные. Шаблон Singleton относится к порождающим, поскольку связан с созданием объекта. 

Данный шаблон ограничивает инициализацию класса, гарантируя создание только одного экземпляра класса для всего приложения. 

Сценарии использования шаблона Singleton

Шаблон Singleton гарантирует, что вся реализация класса вращается вокруг единственного экземпляра, и этой особенностью можно выгодно воспользоваться в разработке. 

Перечислим 3 наиболее часто встречающихся случая применения шаблона.

  1. Операции логирования. 
  2. Операции кэширования. 
  3. Операции базы данных. 

Во всех вышеуказанных случаях не нужно определять отдельный экземпляр при инициализации проекта. Методы класса доступны через предопределенный экземпляр, используемый по всему приложению для выполнения обозначенных действий. Шаблон Singleton позволяет экономить память, поскольку в любой момент времени существует только один экземпляр класса. 

Примеры 

Случай №1. Базовая реализация классов 

Прежде чем реализовать шаблон Singleton, рассмотрим способ создания экземпляра класса и его применение по всему приложению: 

# класс менеджера конфигурации 
class ConfigManager
def initialize

end

def get_configs
...
return configs
end
end

# получение всех данных конфигурации
config_manager = ConfigManager.new
config_manager.get_configs

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

Случай №2. Шаблон Singleton 

class ConfigManager
class<<self
def get_all_configs
...
return configs
end
end
end

# получение всех данных конфигурации
configs = ConfigManager.get_all_configs

В данном коде мы реализуем шаблон Singleton, создавая класс << self. Это позволяет уточнить поведение методов, вызываемых для конкретного объекта. На метод в классе можно ссылаться без явного создания объектов экземпляра. 

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

Случай №3. Шаблон Singleton со встроенным миксином Ruby-Singleton

require 'singleton'

# Определение класса Singleton со встроенным миксином Ruby
class ConfigManager
include Singleton
def get_configs
...
return configs
end
end

# получение всех данных конфигурации
configs = ConfigManager.get_configs

Как видно, можно напрямую задействовать предустановленный миксин Ruby-Singleton, поставляемый с пакетом Ruby. Данный способ характеризуется простотой использования и потокобезопасностью. 

Потокобезопасность 

Суть проблемы с шаблоном Singleton в том, что при выполнении частей приложения в нескольких потоках существует вероятность создания более одного экземпляра класса Singleton из-за состояния гонки. А шаблон Singleton предусматривает наличие только одного экземпляра класса на протяжении всего жизненного цикла приложения. Вследствие этого, наивная реализация классов Singleton не является потокобезопасной. 

Случай №3 с миксином Ruby-Singleton по умолчанию является потокобезопасным при инициализации. Однако нам необходимо обеспечить потокобезопасность реализации методов Singleton

В случае №2 потокобезопасность достигается за счет применения Mutex, взаимоисключающего флага. Он выполняет функции семафора на участке кода, разрешая доступ к одному потоку и блокируя другие:

class ConfigManager
@instance_mutex = Mutex.new

private_class_method :new

def initialize
end


class<<self
# Определение инициализации класса Singleton
def instance
return @instance if @instance
@instance_mutex.synchronize do
@instance ||= new
end
return @instance
end
end

# Возвращение всех данных конфигурации
def get_all_configs
...
return configs
end
end

ConfigManager.instance.get_all_configs

Основная проблема с потоками в наивной реализации Singleton решается путем синхронизации потоков во время первого создания объекта Singleton

Недостатки шаблона Singleton

На примерах мы убедились, как интересно и просто работать с шаблоном Singleton. Однако и у него есть недостатки.

  1. Сложность тестирования. Поскольку конструктор класса Singleton является абстрактным, нужно имитировать инициализацию во время модульного тестирования. 
  2. Предельно внимательная обработка случаев многопоточности
  3. Несоответствие принципам SOLID. Не решает одну проблему за раз.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Muralish Clint: A Beginner’s Guide to Implementing the Singleton Design Pattern in Ruby

Предыдущая статьяСоздание базовых 3D-сцен с помощью Three.js
Следующая статьяКак с помощью Sentry реализовать захват исключений фронтенда