THE BELL

Есть те, кто прочитали эту новость раньше вас.
Подпишитесь, чтобы получать статьи свежими.
Email
Имя
Фамилия
Как вы хотите читать The Bell
Без спама

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

Проблема

Одиночка решает сразу две проблемы, нарушая принцип единственной ответственности класса.

    Гарантирует наличие единственного экземпляра класса . Чаще всего это полезно для доступа к какому-то общему ресурсу, например, базе данных.

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

    Такое поведение невозможно реализовать с помощью обычного конструктора, так как конструктор класса всегда возвращает новый объект.


Клиенты могут не подозревать, что работают с одним и тем же объектом.

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

    Но есть и другой нюанс. Неплохо бы хранить в одном месте и код, который решает проблему №1, а также иметь к нему простой и доступный интерфейс.

Интересно, что в наше время паттерн стал настолько известен, что теперь люди называют «одиночками» даже те классы, которые решают лишь одну из проблем, перечисленных выше.

Решение

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

Если у вас есть доступ к классу одиночки, значит, будет доступ и к этому статическому методу. Из какой точки кода вы бы его ни вызвали, он всегда будет отдавать один и тот же объект.

Аналогия из жизни

Правительство государства - хороший пример одиночки. В государстве может быть только одно официальное правительство. Вне зависимости от того, кто конкретно заседает в правительстве, оно имеет глобальную точку доступа «Правительство страны N».

Структура



    Одиночка определяет статический метод getInstance , который возвращает единственный экземпляр своего класса.

    Конструктор одиночки должен быть скрыт от клиентов. Вызов метода getInstance должен стать единственным способом получить объект этого класса.

Псевдокод

В этом примере роль Одиночки отыгрывает класс подключения к базе данных.

Этот класс не имеет публичного конструктора, поэтому единственный способ получить его объект - это вызвать метод getInstance . Этот метод сохранит первый созданный объект и будет возвращать его при всех последующих вызовах.

// Класс одиночки определяет статический метод `getInstance`, // который позволяет клиентам повторно использовать одно и то же // подключение к базе данных по всей программе. class Database is // Поле для хранения объекта-одиночки должно быть объявлено // статичным. private static field instance: Database // Конструктор одиночки всегда должен оставаться приватным, // чтобы клиенты не могли самостоятельно создавать // экземпляры этого класса через оператор `new`. private constructor Database() is // Здесь может жить код инициализации подключения к // серверу баз данных. // ... // Основной статический метод одиночки служит альтернативой // конструктору и является точкой доступа к экземпляру этого // класса. public static method getInstance() is if (Database.instance == null) then acquireThreadLock() and then // На всякий случай ещё раз проверим, не был ли // объект создан другим потоком, пока текущий // ждал освобождения блокировки. if (Database.instance == null) then Database.instance = new Database() return Database.instance // Наконец, любой класс одиночки должен иметь какую-то // полезную функциональность, которую клиенты будут // запускать через полученный объект одиночки. public method query(sql) is // Все запросы к базе данных будут проходить через этот // метод. Поэтому имеет смысл поместить сюда какую-то // логику кеширования. // ... class Application is method main() is Database foo = Database.getInstance() foo.query("SELECT ...") // ... Database bar = Database.getInstance() bar.query("SELECT ...") // Переменная "bar" содержит тот же объект, что и // переменная "foo".

Применимость

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

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

Когда вам хочется иметь больше контроля над глобальными переменными.

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

Тем не менее, в любой момент вы можете расширить это ограничение и позволить любое количество объектов-одиночек, поменяв код в одном месте (метод getInstance).

Шаги реализации

    Добавьте в класс приватное статическое поле, которое будет содержать одиночный объект.

    Объявите статический создающий метод, который будет использоваться для получения одиночки.

22.10.2015
23:12

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

Без условия уникальности Синглтон является обычной глобальной переменной с учетом вытекающих плюсов и минусов (которых большинство). Поэтому перед тем, как использовать этот паттерн, убедитесь, что он действительно подходит. Объект, который предполагается реализовать в виде Синглтона должен быть по-настоящему единственным на уровне системы.

Пример реализация паттерна Singleton на C++

Технически реализовать объект-Синглтон и использовать его в приложении довольно просто (что подкупает):

#include class MySingleton { public: // Функция-член для доступа к единственному экземпляру static MySingleton* getInstance() { static MySingleton instance; return &instance; } // Наполняем полезным функционалом, как и любой другой класс void test() { std::cout << "Singleton test" << std::endl; } private: // Объявляем конструктор закрытым, чтобы нельзя было // создавать экземпляры класса извне MySingleton() { } }; int main() { // Используем Синглтон MySingleton::getInstance()->test(); // А это работать не будет, поскольку конструктор - закрытый // MySingleton singleton; return 0; }

При желании объект-Синглтон можно адаптировать к многопоточной среде выполнения с помощью мьютексов.

Реклама

Всегда думайте перед созданием Синглтона

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

Рассмотрим пример. Может показаться хорошей идеей создать класс для управления настройками приложения в виде Синглтона. Тогда все компоненты приложения смогут видеть необходимые опции и легко их использовать. С одной стороны, идея кажется довольно неплохой. Конфигурация приложения действительно может быть представлена уникальной сущностью. А свободный доступ к Синглтону упростит использование конфигурации.

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

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

Хотя есть и вполне безобидные применения Синглтонов. Например, при реализации другого паттерна: Абстрактная Фабрика.

Также использование Синглтона оправдано для представления физически уникальных ресурсов компьютера. Например, систему слежения за подключением/отключением USB-устройств уместно реализовать в виде Синглтона.

Одиночка - паттерн, относящийся к группе порождающих паттернов (шиблонов).

Назначение

Гарантирует, что у класса есть только один экземпляр (ещё точнее можно сказать, что у "класса данного типа может быть только один экземпляр" ) , и предоставляет к нему глобальную точку доступа.

Псевдоним

Английское название Singleton (одиночка) часто калькируется в русскоязычные тексты и паттерн Одиночка называют "Синглтоном". Мы же будем использовать русское название так как оно весьма удачно.

Мотивация

Для некоторых классов важно, чтобы существовал только один экземпляр.
Например:

  1. Хотя в системе может быть много принтеров, но возможен лишь один спулер.
  2. Должны быть только одна файловая система и единственный оконный менеджер.
  3. В цифровом фильтре может находиться только один аналого-цифровой преобразователь (АЦП). Бухгалтерская система обслуживает только одну компанию.

Как гарантировать, что у класса есть единственный экземпляр и что этот экземпляр легко доступен?
Глобальная переменная дает доступ к объекту, но не запрещает инстанцировать класс в нескольких экземплярах.
Более удачное решение достигается в том случае, если сам класс:

  1. контролирует то, что у него есть только один экземпляр
  2. может запретить создание дополнительных экземпляров, перехватывая запросы на создание новых объектов
  3. и он же способен предоставить доступ к своему экземпляру.

Известные применения

Применяется во многих системах, моделирующих реальные объекты.

Родственные паттерны

С помощью паттерна одиночка могут быть реализованы многие паттерны.
Смотри описание.

Сегодня я хочу разобрать шаблон проектирования "одиночка" , который очень часто используется в объектно-ориентированном программировании.

Шаблон проектирования "Одиночка" или Pattern Singleton нужен для того, чтобы у нас не было много однотипных объектов, а всегда использовался только один. В качестве примера можно привести класс для работы с базой данных.

Class DB {
protected $db;

Public function __construct() {
$this->
}

Public function get() {}
public function set() {}
public function del() {}
}

$db1 = new DB();
$db2 = new DB();

У нас уже 2 объекта $db1 и $db2 , а потом кто-нибудь, не зная, что уже есть такой объект, создаст третий и т.д. Это очень плохо сказывается на производительности и читаемости кода, а в нашем случае может произойти сбой, т.к. на хостинге ограниченное количество подключений к базе данных.

Чтобы решить эту проблему, и был придуман паттерн singleton .

Class DB {
protected $db;
static private $instance = null;

Private function __construct() {
$this->db = new Mysqli($host, $user, $pass, $database);
}

Private function __clone() {}

Static function getInstance() {
if(self::$instance == null) {
self::$instance = new self();
}
return self::$instance;
}
}

$db = new DB(); // ошибка

$db = DB::getInstance();
$db2 = DB::getInstance();
$db3 = DB::getInstance();

Чтобы создать объект обычным способом было нельзя, мы делаем наш конструктор приватным , но также не забываем и про то, что объекты могут клонироваться и закрываем также метод __clone . Дальше мы создаём статическое свойство $instance , которое по умолчанию равно null . Теперь создаём статический метод getInstance() , который проверяет, равно ли наше статическое свойство null ? Если да, то мы создаём экземпляр нашего объекта и возвращаем его, а если же нет, то просто возвращаем его. Таким образом, у нас всегда будет один и тот же экземпляр, сколько бы мы их не создавали. Использовать его очень просто: присваиваем переменной значение, которое возвращает статический метод getInstance() , класса DB , а дальше работаем, как и с обычным объектом.

Class Singleton { private static $PrS="init private"; public static $PuS="init public"; public static function PrS($A=false) { if($A!==false) self::$PrS=$A; else return self::$PrS; } public static function PuS($A=false) { if($A!==false) self::$PuS=$A; else return self::$PuS; } } echo Singleton::PrS(); echo "\n"; echo Singleton::PuS(); // выведет init private // init public echo "\n -- \n"; $D = new Singleton(); echo $D->PrS(); // также выведет init private echo "\n"; // init public echo $D->PuS(); echo "\n -- \n SET them all!"; // А вот здесь Singleton::PrS("changed private"); // меняем переменные класса Singleton::PuS("changed public"); // используя статическую ссылку echo "\n"; // и попробуем проверить их из "созданного" класса (хотя это просто ссылка копия) echo $D->PrS(); // разумеется, выведет: changed private echo "\n"; echo $D->PuS(); // changed public

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

Слово static для функции говорит о том, что в глобальной таблице при компиляции ей уже выделен адрес - жестко выделен. Так же и со статическими переменными - их адрес также статичен. А НЕстатические переменные (классы) не существуют в адресном пространстве, пока их не определят (оператором new). Обращаться некуда. Для статических адрес уже есть - и к нему (к переменной) можно обратиться всегда - someStaticClass::value

Хотите использовать статический класс для работы с БД - заведите внутри приватную статическую переменную DB_handler. Надо работать с несколькими соединениями (несколько БД) - заведите еще по необходимости. Можно даже соорудить нечто статического массива. Почему нет? Появится необходимость слишком извернуться - перепишите класс. Т.е. копии статических классов вообще не различаются (при изготовлении их оператором new), пока в них не появится хотя бы одна НЕстатическая переменная. Правда, и после этого различаться они будут только этой НЕстатической переменной. Правда, при этом, управлять этой переменной уже получится уже только из изготовленного класса.

Public static function getInstance() { if (is_null(self::$instance)) { self::$instance = new Singleton; } return self::$instance; }

Вот этот кусок кода как раз и возвращает копию ссылки на адрес статического класса Singleton. Это то же самое, что написать Singleton::(и там что-то)

Вот об этом и был вопрос - "ЗАЧЕМ?". Ответ простой - да, НЕЗАЧЕМ:)

Наверное, есть задачи, где надо заводить экземпляр класса Singleton, "...а вот если не было обращений (ну не потребовалось что-то), то ничего не заведется и все будет тихо и спокойно... А вот вроде как статический класс будет существовать даже тогда, когда он может не понадобиться... и ух, как страшно съест памяти... " В общем, как-то я не могу вот так с ходу придумать такой задачи, чтобы применять именно СОЗДАНИЕ классов вместо статических классов.

И вот я например, тоже не вижу разницы между сложным Singleton наворотом и простым Singleton::doAction(). Вообще, статические классы (со статическими переменными) чрезвыйчано удобны еще и тем, что они предоставляют как бы "глобальные" переменные для любой области видимости. И хэндлер для БД тому яркий пример.

THE BELL

Есть те, кто прочитали эту новость раньше вас.
Подпишитесь, чтобы получать статьи свежими.
Email
Имя
Фамилия
Как вы хотите читать The Bell
Без спама