Задумывались ли вы когда-нибудь, занимаясь объектно-ориентированным программированием, почему всегда нужно создавать экземпляр класса? В ряде случаев достаточно только одного экземпляра, который предусматривает возможность глобального использования после инициализации.
Некоторые программисты объявляют экземпляр класса, присваивают его глобальной переменной при запуске проекта и применяют его на протяжении всей разработки. А есть ли вариант получше глобальной переменной? Да, классы Singleton
!
Singleton
(в переводе на русский язык — “одиночка”) представляет собой порождающий шаблон проектирования. Он изначально был одним из шаблонов, описанных так называемой “бандой четырех”, куда входили авторы: Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес.
Понятие шаблона проектирования Singleton
В своей книге “Приёмы объектно-ориентированного проектирования. Паттерны проектирования” “банда четырех” описывает 23 шаблона, которые подразделяются на 3 категории: порождающие, поведенческие и структурные. Шаблон Singleton относится к порождающим, поскольку связан с созданием объекта.
Данный шаблон ограничивает инициализацию класса, гарантируя создание только одного экземпляра класса для всего приложения.
Сценарии использования шаблона Singleton
Шаблон Singleton
гарантирует, что вся реализация класса вращается вокруг единственного экземпляра, и этой особенностью можно выгодно воспользоваться в разработке.
Перечислим 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
. Однако и у него есть недостатки.
- Сложность тестирования. Поскольку конструктор класса
Singleton
является абстрактным, нужно имитировать инициализацию во время модульного тестирования. - Предельно внимательная обработка случаев многопоточности.
- Несоответствие принципам SOLID. Не решает одну проблему за раз.
Читайте также:
- Разбор методов Ruby
- Как хранить и кодировать видео посредством Ruby on Rails, Lambda и S3
- Как и почему я перешёл с Ruby на Python
Читайте нас в Telegram, VK и Дзен
Перевод статьи Muralish Clint: A Beginner’s Guide to Implementing the Singleton Design Pattern in Ruby