Как стать автором
Обновить

Комментарии 31

SRP забыли.
Приватный конструктор + френд на класс An забыли.
Френд на класс будет описан в последующих статьях по мере развития класса. Не совсем понятно, зачем нужен приватный конструктор?
Чтобы не создать синглтон как-то иначе.
An — это не класс-синглтон, его можно создавать как угодно. Синглтон заливается через функцию anFill. An можно копировать и иметь несколько экземпляров без каких-либо проблем.
SRP не упомянул, хотя можно было бы. На мой взгляд, данный факт является более теоретическим и является довольно спорным. Но, как несложно догадаться, приведенный подход решает также и эту проблему.
«Паттерн» уже, конечно, в русскоязычной литературе определение устоявшееся, к сожалению, но «синглтон»… Лучше уж тогда родное название использовать.
В русскоязычной литературе чаще используется слово синглтон, чем одиночка. Поэтому я посчитал, что будет более понятнее и естественнее использовать это слово.
Данная статья немного о другом: в ней описываются различные реализации синглтонов, в то время как я хотел показать его использование.
Про многопоточность хорошо бы упомянуть сразу — ваш синглтон (иногда по книжке называемый синглтон Майерса) в таком виде не «threadsafe»…
Будет про многопоточность, но позже. Невозможно охватить все аспекты в одной статье, т.к. хочется подробно описать такой важный вопрос. Я планирую продолжение этого топика с освещением вопросов про время жизни, многопоточность и различные плюшки.
Статья хорошая, просто рекомендую приписать, что в таком виде синглтон Майерса не «threadsafe» — там, где говорите, что многопоточность будет позже…
Поправил
Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, как ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает неявную зависимость подсистем друг от друга и, как следствие, серьезно усложняет разработку.

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

Нет?

А за статью спасибо, интересно.
В целом да, но в базе данных есть один трюк. И хотя мы не знаем состояния, мы открываем транзакцию и на некоторое время фиксируем состояние грубо говоря. Потом начинаем выяснять это состояние (SELECT), и затем — изменять (INSERT,UPDATE,DELETE). Т.е. процесс происходит так, как будто мы ничего не знаем о текущем состоянии. При этом транзакция гарантирует, что никто не вмешается и не испортим нам получение состояния. Поэтому такая транзакционность упрощает взаимодействие с базой данных.

В целом, не стоит воспринимать данный тезис как то, что происходит всегда без исключений. Но об этом все же стоит помнить при проектировании/использовании.
>>И хотя мы не знаем состояния, мы открываем транзакцию и на некоторое время фиксируем состояние грубо говоря. Потом начинаем выяснять это состояние (SELECT), и затем — изменять (INSERT,UPDATE,DELETE).

Вы описали какой-то предельный случай. В жизни все несколько проще, никто не делает SELECT перед каждым INSERT.

Дам контрпример.

У меня есть объект логгера. Он один на приложение и даже на группу приложений. Знать его состояние для меня — избыточно, оно меня не волнует совершенно. Если что-то случится — он выбросит исключение.

А вот иметь такой объект ровно один (поскольку для лога должен быть только один вход) — жизненно необходимо.

Резюме — понятие «состояние» имеет косвенное отношение к паттерну «одиночка».
Для логгера есть состояние, например — открытый файл. Если файл закрыт, то в лог писать нет смысла. К тому же файл могут удалить и логи перестанут записываться, если не сделать дополнительные приседания. В данном случае вызывающему объекту не нужно знать о состоянии, синглтон его должен поддерживать самостоятельно. Но об этом важно помнить.
Для логгера есть состояние, например — открытый файл. Если файл закрыт, то в лог писать нет смысла.

А зачем мне об этом вообще знать?
Надо будет — логгер бросит исключение. Не бросил — отлично, значит продолжаем.

Инкапсуляция же? Зачем свое состояние объект вообще должен кому-либо показывать? Есть интерфейсы, вот ими и пользуйтесь.
Да что я всё не на то сообщение-то отвечаю… Сорри.
Имелось в виду то, что у реализации синглтона есть состояние, но интерфейс, конечно же, должен по возможности скрывать подробности реализации и сложности, с которыми сталкивается такая реализация. В принципе, мы говорим об одном и том же.
Зачем там куча шаблонных классов, которые только запутывают понимание всего происходящего?
В чем преимущество Вашего решения пред следующим кодом?
//Singleton.h
class Singleton
{
private:
    Singleton();
    //disallow copy and assign
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
public:
    Singleton& GetInstance();
};
.....
//Singleton.cpp
Singleton& Singleton::GetInstance()
{
    static Singleton g_Instance;
    return g_Instance;
}
Singleton::Singleton()
{
}


Про шаблонный Singleton очень хорошо написано в книге Адександреску — Современное проектирование на c++
И кстати есть готовое решение — библиотека Loki
Ответ заключается в том, что такой подход содержит в себе все недостатки, перечисленные в начале статьи. Как следствие, при кажущейся простоте, появляется трудность при расширении и превращении синглтона не в синглтон. Например, мы знаем, что у нас есть один экран и поэтому логично использовать синглтон для отрисовки изображения. Но потом появляется второй экран и выясняется, что все надо переделывать. Предложенный подход не настаивает на использовании синглтона, и в следующих статьях я покажу, как можно это использовать.
Если предполагается заменять Singleton на не Singleton, то это явно ошибка в проектировании, и паттерн Singleton здесь ни при чем. Singleton это изначально один глобальный объект, он создается как один, и не предполагает наличия нескольких копий, это суть паттерна, которую нельзя нарушать!

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

Я видел в реальном проекте использование нескольких копий объекта Singleon, это было просто ужасно!
О том-то и речь — как использовать синглтон так, чтобы, если в будущем понадобится его заменить на что-то другое, не пришлось перелопачивать горы кода и заменять все использования синглтона.
как то у вас не согласуется факт чтения Александреску и приведенный код)
плюшка предложенного топикстартером решения — универсальность, мы один раз пишем реализацию синглтона и сотни раз ее используем с разными классами, в вашем же случае придется для каждого класса писать реализацию отдельно, это как минимум
По Вашему мнению человек, прочитавший Александреску должен тут-же накладывать тонны запутанного кода только лишь ради универсальности?
Универсальность — это удел библиотек, таких как буст и stl, к коим код данной стати, как говорится, и в подметки не годится.
Тот-же Александреску в другой своей книге пишет что «Главное — корректность, простота и ясность»
Стандарты проектирования на C++. 101 правило и рекомендации.

Книгу я привел в пример потому, что в ней рассматривается паттерн Singleton настолько детально, что мало ли что еще можно туда добавить.
Также решение, сделанное в библиотеке Loki (про которую и рассказывается в книге) довольно универсальное и сделанное на основе стратегий — можно настраивать детали реализации при помощи параметров шаблона, а не принимать компромиссные решения, навязываемые библиотекой, чего нет в данной статье.
Боюсь, вы не поняли основной посыл статьи. Речь в ней идет не о реализации, а об использовании. Собственно, это даже написано в заголовке. При этом реализацию можно взять из предложенного вами куска кода. Можно взять реализацию из Александреску. Книжка его очень умная и толковая. Но вся статья написана о том, как использовать и убрать недостатки, присущие этому паттерну.
Я и вправду не понимаю суть решения, по этому и задал вопрос.

Если можно вызывать An()->action(); в любом месте программы, то в чем его отличие от вызова GetInstance(), как это решает проблему декларации использования класса Singleton в произвольном классе?
Задекларировать использование Singleton в классе, можно при помощи ссылки либо указателя на Singleton, также как автор писал An x; в классе Y.
Какое принципиальное отличия между использованием представленного шаблона An<> и простого указателя (ссылки)?
Может быть в продолжении это будет как-то более понятно, но в контексте данной статьи шаблон An мне кажется спорным.

Но в любом случае, автору спасибо за статью, пишите еще!
Во второй статье будет более «выпукло», когда речь зайдет о времени жизни.
НЛО прилетело и опубликовало эту надпись здесь
А что по поводу тестируемости? Как сделать mock-объект при таком подходе? И как реализовать dependency injection?
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории