Комментарии 61
На последний вариант — все же если несколько потоков запросят Instance — создадутся несколько копий синглтона. В ссылку instance запишется только одна из них конечно, и все потоки получат только одну, но тем не менее. Если это например, логгер, который сразу должен открыть файл, или коннект к базе данных, то такой вариант неприемлем. Или если класс «тяжелый», что его создание и ининциализация «дороже», чем lock, то использование интерлока просто невыгодно.
Скажем так — это приемлемо если должен «жить» только один объект, а если же должен быть «создан» только один объект, то нет.
Вариант с lock() как раз таки гарантирует создание только одного объекта, что под синглтон больше подходит и по совместимости/переносимости в частности и по логике вообще.
Скажем так — это приемлемо если должен «жить» только один объект, а если же должен быть «создан» только один объект, то нет.
Вариант с lock() как раз таки гарантирует создание только одного объекта, что под синглтон больше подходит и по совместимости/переносимости в частности и по логике вообще.
И вообще — имхо, «экономия» на операциии, которая происходит один раз есть овчинка не стоящая выделки.
Я тоже не вижу особых проблем с double lock-ом — всё-таки все его недостатки уже успели устареть. Хотя и с Рихтером согласен — try-catch в этом случае не нужен.
Другое дело, что варианты с readonly и nested-классом расползлись по уйме книг и теперь много кто уверен, что именно «хаками» такое и надо писать.
Другое дело, что варианты с readonly и nested-классом расползлись по уйме книг и теперь много кто уверен, что именно «хаками» такое и надо писать.
Недопонял по поводу последнего кусочка кода. Как он позволяет обработать ситуацию, при которых потоки, находясь на инструкции
?
if(instance != null) return instance;
считывают null и переходят к инструкции Singleton temp = new Singleton();
?
Всё вы правильно поняли :). Поток создаёт temp, а потом _атомарно_ переносит его через Interlocked.CompareExchange. В итоге следующий поток уже не может ничего записать — Interlocked же.
Последний вариант я взял из CLR via C# by Jeffry Richter, последняя, 29-я глава. Там довольно подробно объясняется, почему так лучше.
Последний вариант я взял из CLR via C# by Jeffry Richter, последняя, 29-я глава. Там довольно подробно объясняется, почему так лучше.
Хитро, конечно, придумано, возьму на заметку. Хотя, мне кажется, что идиома синглетона рушится таким подходом — ведь когда видят синглетон, справедливо полагают, что он должен конструироваться один раз. А тут вот так вычурно — не всем такой способ подойдет. Да и код с первого взгляда не понятен — кажется, что в нем допущена ошибка, и руки тянутся вписать блокировку :)
Вообще, я лично остановился на double checked locking синглетонах. Если уж мне нужно сделать синглетон, и если он должен быть потокобезопасным — я как правило выбираю либо этот способ, либо просто статическое поле, инициализируемое в статическом же конструкторе (это если мне вообще без разницы, когда будет создан синглетон — ленивость не нужна). Все остальные способы либо страдают чем-то, либо зависят от деталей того, как рантайм или компилятор обрабатывают всякие тонкие моменты типа порядка инициализации статических переменных, beforefieldinit итд, либо чересчур сложны для решаемой задачи (черт возьми, ведь мне всего-навсего нужен долбанный синглетон! мне не хочется использовать для этого нюансы конкретно используемого языка). Про это вы как раз очень в точку отметили:
С производительностью у DCL тоже все в порядке — он проигрывает самым оптимально написанным синглетонам сущие копейки. Поэтому мой выбор — DCL, пусть он и не самый «идеальный» вариант с точки зрения перфекционистов.
Почему нехорошо «обманывать» язык? Потому что каждый такой «хак» очень легко нечаянно поломать. И потому что от него нет никакой пользы людям, которые пишут на других языках — а ведь паттерн предполагает универсальность.
С производительностью у DCL тоже все в порядке — он проигрывает самым оптимально написанным синглетонам сущие копейки. Поэтому мой выбор — DCL, пусть он и не самый «идеальный» вариант с точки зрения перфекционистов.
А как думаете, не лучше ли вместо Lock писать нативный Monitor.Enter? Я тут больше ко мнению Jeffry Richter'а склоняюсь — сгенерированный комплятором try здесь а) тормозит б) если отвалится — уже не поможет.
На мой взгляд, внутри double-checked-lock'а (раз уж поток зашел внутрь) уже нет смысла экономить на спичках, поскольку этот код выполняется 1 раз за всю жизнь приложения, ну может быть 2 раза у самых-самых везучих.
А если конструктор обвалится? Например, зашёл один поток — обвалил конструктор, заходит второй поток — и снова обваливает конструктор, делая ещё хуже.
А как хуже-то? Повторно будет писаться в лог о том, что произошла ошибка? Или повторный try-finally приведет к деградации производительности? По сравнению с тем фактом, что *синглетон не удалось инстанцировать из-за исключения в конструкторе*, проблемы такого рода представляются мне малозначимыми :)
А что будет в случае интерлоков? Предполагая, что конструкторы везде одинаковы и различие лишь в синглтоновом методе Instance — обвалятся точно так же. Только параллельно в разных тредах, а не последовательно в случае лока.
Даже я бы сказал — будет ровно наоборот, если речь идет об открытии файла на запись, например, то в случае лока все пройдет нормально, а в случае интерлока получим одно нормальное выполнение и (n-1) исключений в других потоках, сигнализирующих о невозможности открыть файл. И эти ситуации еще нужно обработать как то, чтобы эти потоки получили нормальный instance, а не null.
Даже я бы сказал — будет ровно наоборот, если речь идет об открытии файла на запись, например, то в случае лока все пройдет нормально, а в случае интерлока получим одно нормальное выполнение и (n-1) исключений в других потоках, сигнализирующих о невозможности открыть файл. И эти ситуации еще нужно обработать как то, чтобы эти потоки получили нормальный instance, а не null.
Насколько
public static class Singleton
, при использовании C#, хуже всего вышеописанного?Не намного. Но есть свои особенности. Например:
— static класс невозможно от чего-то унаследовать — а значит, не получится собрать абстрактную фабрику (фабрики рекомендуют объявлять синглтонами ещё в самой первой книжке).
— когда выполнится static-конструктор — никто не знает. Вроде бы «где-то в начале». К тому же, для ASP.Net-приложений может быть актуальным совмещение фабрика по производству синглтонов :) — по синглтону на сессию. Ведь в ASP.Net статичные переменный глобальны для всего приложения.
— Когда падает конструктор статического класса — всё падает. Когда падает конструктор синглтона — падает только функция, которая неосторожно его вызвала
— Невозможность засунуть в статичный класс счётчик состояний (например, чтобы можно было откатить его к предыдущим настройкам).
— static класс невозможно от чего-то унаследовать — а значит, не получится собрать абстрактную фабрику (фабрики рекомендуют объявлять синглтонами ещё в самой первой книжке).
— когда выполнится static-конструктор — никто не знает. Вроде бы «где-то в начале». К тому же, для ASP.Net-приложений может быть актуальным совмещение фабрика по производству синглтонов :) — по синглтону на сессию. Ведь в ASP.Net статичные переменный глобальны для всего приложения.
— Когда падает конструктор статического класса — всё падает. Когда падает конструктор синглтона — падает только функция, которая неосторожно его вызвала
— Невозможность засунуть в статичный класс счётчик состояний (например, чтобы можно было откатить его к предыдущим настройкам).
Не намного. Но есть свои особенности. Например:
— static класс невозможно от чего-то унаследовать — а значит, не получится собрать абстрактную фабрику (фабрики рекомендуют объявлять синглтонами ещё в самой первой книжке).
— когда выполнится static-конструктор — никто не знает. Вроде бы «где-то в начале». К тому же, для ASP.Net-приложений может быть актуальным совмещение фабрика по производству синглтонов :) — по синглтону на сессию. Ведь в ASP.Net статичные переменный глобальны для всего приложения.
— Когда падает конструктор статического класса — всё падает. Когда падает конструктор синглтона — падает только функция, которая неосторожно его вызвала
— Невозможность засунуть в статичный класс счётчик состояний (например, чтобы можно было откатить его к предыдущим настройкам).
— static класс невозможно от чего-то унаследовать — а значит, не получится собрать абстрактную фабрику (фабрики рекомендуют объявлять синглтонами ещё в самой первой книжке).
— когда выполнится static-конструктор — никто не знает. Вроде бы «где-то в начале». К тому же, для ASP.Net-приложений может быть актуальным совмещение фабрика по производству синглтонов :) — по синглтону на сессию. Ведь в ASP.Net статичные переменный глобальны для всего приложения.
— Когда падает конструктор статического класса — всё падает. Когда падает конструктор синглтона — падает только функция, которая неосторожно его вызвала
— Невозможность засунуть в статичный класс счётчик состояний (например, чтобы можно было откатить его к предыдущим настройкам).
Вывод один — надо знать свой язык, тогда любой паттерн можно реализовать грамотно и красиво!
я нисколько не знаю C++, но именно такие статьи, разбирающие глубокие технические аспекты технических тем, вызывающие уйму комментариев с разбором полётов на эту же тему, делают хабр «тем». искреннее вам спасибо! лучи плюсов вам в карму и профессиональных успехов!
> Почему нехорошо «обманывать» язык? Потому что каждый такой «хак» очень легко нечаянно поломать.
Как можно нечаянно поломать вариант c Lazy?
> И потому что от него нет никакой пользы людям, которые пишут на других языках — а ведь паттерн
> предполагает универсальность.
Паттерн это прежде всего сама идея. А оптимизировать код для переноса между языками надо только если стоит такая задача.
Как можно нечаянно поломать вариант c Lazy?
> И потому что от него нет никакой пользы людям, которые пишут на других языках — а ведь паттерн
> предполагает универсальность.
Паттерн это прежде всего сама идея. А оптимизировать код для переноса между языками надо только если стоит такая задача.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Не стоит также забывать, что в наше время Синглтон уже считается антипаттерном (Wikipedia, 2й параграф)
Ага, и практически в каждом приложении используется IoС контейнер, который сам разруливает «синглетонность».
Меня удивляет постоянное всплывание темы потокобезопасных синглтонов, соглашусь в одним из предыдущих ораторов с тем, что написание и возможные проблемы от ошибок в синхронизации стоят больше нежели лишнее время на немедленное инициализирование при старте приложения.
Меня удивляет постоянное всплывание темы потокобезопасных синглтонов, соглашусь в одним из предыдущих ораторов с тем, что написание и возможные проблемы от ошибок в синхронизации стоят больше нежели лишнее время на немедленное инициализирование при старте приложения.
Вики, конечно, редкостный авторитет в этих вопросах. Не говоря уже о том, что там написано «некоторые считают», а не «считается».
Ну и да, аргументируйте своими словами, почему синглтон как паттерн (а не предложенные реализации!) — это антипаттерн.
Ну и да, аргументируйте своими словами, почему синглтон как паттерн (а не предложенные реализации!) — это антипаттерн.
Ошибся веткой. Мой ответ ниже
НЛО прилетело и опубликовало эту надпись здесь
Статья родилась из рабочих заметок, так что многое просто не пояснил.
Он тормозной потому, что два try/finally делает. Поэтому мне кажется, что лучше напрямую писать Monitor.Enter/Exit — будет то же самое, но без лишних обработчиков.
Ну и сам факт того, что представляет из себя lock интересен. А то находятся люди, которые его ещё раз try-ем оборачивают.
Он тормозной потому, что два try/finally делает. Поэтому мне кажется, что лучше напрямую писать Monitor.Enter/Exit — будет то же самое, но без лишних обработчиков.
Ну и сам факт того, что представляет из себя lock интересен. А то находятся люди, которые его ещё раз try-ем оборачивают.
ОК, я был слишком краток ссылаясь на Википедию. На самом деле там есть ссылки на статьи которые объясняют почему это считается антипеттерном. К слову, я специально написал «считается» вместо «является».
Во времена написания «GoF» синглтон был актуальным шаблоном. В настоящее время подходы к разработке изменились. Модульные тесты являются практически стандартной инженерной практикой. При написании тестов Синглтоны оказываются проблемой. Заменить mock'ом без модификации его практически не возможно.
Вторая неприятность с Синглтоном — он делает зависимости неочевидными. Глядя на интерфейс класса, невозможно узнать увидеть все его зависимости. Для этого нужно смотреть реализацию медодов.
Проблема, которую он решает Синглтон — это убедиться, что в системе существует один, и только один экземпляр ресурса. В подовляющем большинстве случаев эту проблему можно решить другим способом — используя Dependency Injection.
Синглтон имеет право на существование, и нужно занать как его правильно реализовать. Но каждый раз, когда вы собираетесь его использовать, вы должны доказать себе и тому, кто будет мейнтейнить Ваш код, что в этой ситуации Синглтон действительно необходим.
Во времена написания «GoF» синглтон был актуальным шаблоном. В настоящее время подходы к разработке изменились. Модульные тесты являются практически стандартной инженерной практикой. При написании тестов Синглтоны оказываются проблемой. Заменить mock'ом без модификации его практически не возможно.
Вторая неприятность с Синглтоном — он делает зависимости неочевидными. Глядя на интерфейс класса, невозможно узнать увидеть все его зависимости. Для этого нужно смотреть реализацию медодов.
Проблема, которую он решает Синглтон — это убедиться, что в системе существует один, и только один экземпляр ресурса. В подовляющем большинстве случаев эту проблему можно решить другим способом — используя Dependency Injection.
Синглтон имеет право на существование, и нужно занать как его правильно реализовать. Но каждый раз, когда вы собираетесь его использовать, вы должны доказать себе и тому, кто будет мейнтейнить Ваш код, что в этой ситуации Синглтон действительно необходим.
Ваш комментарий — это наглядный пример путаницы между собственно паттерном и его реализациями.
Сначала давайте посмотрим, что такое синглтон. Из вики: «the singleton pattern is a design pattern that restricts the instantiation of a class to one object». Из Каннингема: «Ensure a class has only one instance, and provide a global point of access to it.» GoF под рукой, к сожалению, нет, но насколько я знаю, тамошнее определение у Каннингема и процитировано.
Обратите внимание, что оба этих определения акцентируют внимание на одном и том же: существовании только одного экземпляра класса (кстати, не обязательно в системе, речь может идти и о более узком scope). То есть, для паттерна важно, что есть один экземпляр класса (и было бы удобно, если бы к нему можно было получить прозрачный и униформный доступ).
А вот теперь рассмотрим ваши примеры.
Что вы хотите протестировать? Очевидно, не сам синглтон, потому что при его тестировании никаких проблем не возникет. Вероятно, вы хотите протестировать класс, зависящий от синглтона. Но эта постановка задачи неверна — класс должен зависеть от абстракции, а не от конкретной реализации (иначе бы никакие замены были невозможно, и мы бы не говорили о модульном тестировании). Следовательно, то, что вы тестируете, должно рассчитывать на получение экземпляра определенной абстракции (интерфейса, класса — не так важно), и, в принципе, даже не обязано знать о том, сколько этих экземпляров в системе. Следующий вопрос — откуда этот экземпляр взять, и вот он к синглтону как паттерну отношения не имеет.
Эта проблема вообще не имеет отношения к синглтону. Это типичная проблема любых неявных зависимостей (статических фабрик, сервис-локаторов и так далее). Соответственно, вам просто нужно выбрать (общий для всего проекта) стиль передачи/получения зависимостей (и у явного, и у неявного есть свои достоинства и недостатки) и придерживаться его.
Дело в том, что вы путаете две вещи.
Смотрите, два примера выше уже должны были натолкнуть на мысль, что с точки зрения кода-пользователя синглтон — это просто зависимость. Как любая зависимость, он должен быть абстрагирован (IoC). Соответственно, после этого все пользователи должны рассматривать эту функциональную единицу как еще одну зависимость, работать с которой нужно точно так же, как и со всеми другими. При этом эта зависимость может быть синглтоном (и все пользователи получат один экземпляр), а может им не быть (и тогда все получат свой).
Разделяйте dependency management и instance/construction/lifetime management. Мы можем взять DI как парадигму и Unity как IoC-контейнер, реализовать DI через вбрасывание в конструктор, а для конкретной зависимости указать, что ее Lifetime manager — ContainerControlled. Получим синглтон в DI — потому что у класса есть ровно один экземпляр. Можем сделать все то же самое, но выбрать не DI, а ServiceLocation (например, потому, что у нас нет контроля за созданием объектов) — получим синглтон через локатор (визуально практически не отличающийся от обычного синглтона).
Повторю еще раз логическую цепочку:
Общий вывод: синглтон как паттерн создания объектов не устарел (и антипаттерном не является). Устарели его реализации, большую часть которых сейчас можно рассматривать как узкозаточенный сервис-локатор со всеми недостатками сервис-локаторов.
Сначала давайте посмотрим, что такое синглтон. Из вики: «the singleton pattern is a design pattern that restricts the instantiation of a class to one object». Из Каннингема: «Ensure a class has only one instance, and provide a global point of access to it.» GoF под рукой, к сожалению, нет, но насколько я знаю, тамошнее определение у Каннингема и процитировано.
Обратите внимание, что оба этих определения акцентируют внимание на одном и том же: существовании только одного экземпляра класса (кстати, не обязательно в системе, речь может идти и о более узком scope). То есть, для паттерна важно, что есть один экземпляр класса (и было бы удобно, если бы к нему можно было получить прозрачный и униформный доступ).
А вот теперь рассмотрим ваши примеры.
Модульные тесты являются практически стандартной инженерной практикой. При написании тестов Синглтоны оказываются проблемой. Заменить mock'ом без модификации его практически не возможно.
Что вы хотите протестировать? Очевидно, не сам синглтон, потому что при его тестировании никаких проблем не возникет. Вероятно, вы хотите протестировать класс, зависящий от синглтона. Но эта постановка задачи неверна — класс должен зависеть от абстракции, а не от конкретной реализации (иначе бы никакие замены были невозможно, и мы бы не говорили о модульном тестировании). Следовательно, то, что вы тестируете, должно рассчитывать на получение экземпляра определенной абстракции (интерфейса, класса — не так важно), и, в принципе, даже не обязано знать о том, сколько этих экземпляров в системе. Следующий вопрос — откуда этот экземпляр взять, и вот он к синглтону как паттерну отношения не имеет.
Вторая неприятность с Синглтоном — он делает зависимости неочевидными. Глядя на интерфейс класса, невозможно узнать увидеть все его зависимости. Для этого нужно смотреть реализацию медодов.
Эта проблема вообще не имеет отношения к синглтону. Это типичная проблема любых неявных зависимостей (статических фабрик, сервис-локаторов и так далее). Соответственно, вам просто нужно выбрать (общий для всего проекта) стиль передачи/получения зависимостей (и у явного, и у неявного есть свои достоинства и недостатки) и придерживаться его.
Проблема, которую он решает Синглтон — это убедиться, что в системе существует один, и только один экземпляр ресурса. В подовляющем большинстве случаев эту проблему можно решить другим способом — используя Dependency Injection.
Дело в том, что вы путаете две вещи.
Смотрите, два примера выше уже должны были натолкнуть на мысль, что с точки зрения кода-пользователя синглтон — это просто зависимость. Как любая зависимость, он должен быть абстрагирован (IoC). Соответственно, после этого все пользователи должны рассматривать эту функциональную единицу как еще одну зависимость, работать с которой нужно точно так же, как и со всеми другими. При этом эта зависимость может быть синглтоном (и все пользователи получат один экземпляр), а может им не быть (и тогда все получат свой).
Разделяйте dependency management и instance/construction/lifetime management. Мы можем взять DI как парадигму и Unity как IoC-контейнер, реализовать DI через вбрасывание в конструктор, а для конкретной зависимости указать, что ее Lifetime manager — ContainerControlled. Получим синглтон в DI — потому что у класса есть ровно один экземпляр. Можем сделать все то же самое, но выбрать не DI, а ServiceLocation (например, потому, что у нас нет контроля за созданием объектов) — получим синглтон через локатор (визуально практически не отличающийся от обычного синглтона).
Повторю еще раз логическую цепочку:
- для модульного тестирования нужен IoC (он нужен не только для этого, но это приведенный вами пример)
- с точки зрения IoC синглтонов не существует, потому что синглтон — это реализация некоей функциональности, а потребители в IoC оперируют абстракциями, а не реализациями
- для того, чтобы внедрить IoC, вам нужно управление зависимостями (явное, с помощью DI, или неявное, с помощью service location, фабрик и так далее)
- каждая зависимость имеет разный жизненный цикл. Синглтон является одним из вариантов жизненного цикла зависимости
Общий вывод: синглтон как паттерн создания объектов не устарел (и антипаттерном не является). Устарели его реализации, большую часть которых сейчас можно рассматривать как узкозаточенный сервис-локатор со всеми недостатками сервис-локаторов.
Аргументрованные споры я люблю :)
Про реализацию синглтона я не сказал ни слова, автор топика все отлично описал. Я сказал что этот паттерн в принципе решает одну проблему, но при этом добавляет еще две.
Вы приводите аргументы, подтверждающие проблемность Синглтона, но делаете противополжный вывод :)
Вот где Синглтон создает проблему.
Допустим у меня есть какой-то ресурс к которому нужен монопольный доступ. Синглтон предлагает изолировать его и предоставить один метод для доступа к ресурсу, скажем MyResource.getInstance().
Теперь у меня есть класс, для которого я хочу написать юнит-тест, но он использует Синглтон. В юнит тесте я не хочу создавать реальный экземпляр ресурса. Я хочу заменить его моком. Как мне это сделать? В книге «Working effectively with legacy code» предлагается сделать лазейку в виде сеттера MyResource.setInstance(). Но тогда есть опасность что этот метод будет использован кроме тестов еще и в продакшн коде.
Абсолютно верно.
Вместо в место Синглтонов и статических фабрик предпочтительнее использовать DI. Это позволит избежать описанной выше проблемы с тестированием.
Нужно разделять стадию Construction и стадию выполнения. На стадии выполния следует использовать те зависимости, которые были предоставлены DI контейнером, а не использовать Синглтон.
На стадии конструирования у нас есть полный контроль на тем сколько экземпляров мы создали, и в использовании Синглтона нет необходимости.
Общий вывод: На стадии конструирования Синглтон не нужен, а на стадии выполния его следует избегать.
Про реализацию синглтона я не сказал ни слова, автор топика все отлично описал. Я сказал что этот паттерн в принципе решает одну проблему, но при этом добавляет еще две.
Вы приводите аргументы, подтверждающие проблемность Синглтона, но делаете противополжный вывод :)
Вероятно, вы хотите протестировать класс, зависящий от синглтона. Но эта постановка задачи неверна — класс должен зависеть от абстракции, а не от конкретной реализации (иначе бы никакие замены были невозможно, и мы бы не говорили о модульном тестировании). Следовательно, то, что вы тестируете, должно рассчитывать на получение экземпляра определенной абстракции (интерфейса, класса — не такАбсолютно верно.
важно), и, в принципе, даже не обязано знать о том, сколько этих экземпляров в системе.
Следующий вопрос — откуда этот экземпляр взять, и вот он к синглтону как паттерну отношения не имеет.
Вот где Синглтон создает проблему.
Допустим у меня есть какой-то ресурс к которому нужен монопольный доступ. Синглтон предлагает изолировать его и предоставить один метод для доступа к ресурсу, скажем MyResource.getInstance().
Теперь у меня есть класс, для которого я хочу написать юнит-тест, но он использует Синглтон. В юнит тесте я не хочу создавать реальный экземпляр ресурса. Я хочу заменить его моком. Как мне это сделать? В книге «Working effectively with legacy code» предлагается сделать лазейку в виде сеттера MyResource.setInstance(). Но тогда есть опасность что этот метод будет использован кроме тестов еще и в продакшн коде.
Эта проблема вообще не имеет отношения к синглтону. Это типичная проблема любых неявных зависимостей (статических фабрик, сервис-локаторов и так далее). Соответственно, вам просто нужно выбрать (общий для всего проекта) стиль передачи/получения зависимостей (и у явного, и у неявного есть свои достоинства и недостатки) и придерживаться его.
Абсолютно верно.
Вместо в место Синглтонов и статических фабрик предпочтительнее использовать DI. Это позволит избежать описанной выше проблемы с тестированием.
Разделяйте dependency management и instance/construction/lifetime management. Мы можем взять DI как парадигму и Unity как IoC-контейнер, реализовать DI через вбрасывание в конструктор, а для конкретной зависимости указать, что ее Lifetime manager — ContainerControlled. Получим синглтон в DI — потому что у класса есть ровно один экземпляр. Можем сделать все то же самое, но выбрать не DI, а ServiceLocation (например, потому, что у нас нет контроля за созданием объектов) — получим синглтон через локатор (визуально практически не отличающийся от обычного синглтона).Абсолютно верно.
Нужно разделять стадию Construction и стадию выполнения. На стадии выполния следует использовать те зависимости, которые были предоставлены DI контейнером, а не использовать Синглтон.
На стадии конструирования у нас есть полный контроль на тем сколько экземпляров мы создали, и в использовании Синглтона нет необходимости.
Общий вывод: На стадии конструирования Синглтон не нужен, а на стадии выполния его следует избегать.
Синглтон предлагает изолировать его и предоставить один метод для доступа к ресурсу, скажем MyResource.getInstance().
Нет. Синглтон предлагает предоставить глобальный доступ. А вариант, который предлагаете вы (статический метод класса) — это всего лишь вариант реализации глобального доступа. С равным успехом вы можете сделать ServiceLocator.Resolve(typeof(MyResource)) или .ctor(Func{of MyResource} resourceGetter).
В книге «Working effectively with legacy code» предлагается сделать лазейку в виде сеттера MyResource.setInstance(). Но тогда есть опасность что этот метод будет использован кроме тестов еще и в продакшн коде.
Типовой решение — не собирать этот метод для продакшн-кода (или статический анализ, или отдельная сборка и internal-access — решений много).
Вместо в место Синглтонов и статических фабрик предпочтительнее использовать DI.
Вы, кажется, не совсем поняли, что я сказал. Правильной формулировкой было бы: «вместо service location и фабрик предпочтительно использовать DI», и это утверждение, скажем так, не совсем верно, потому что у явных зависимостей есть свои недостатки.
На стадии конструирования у нас есть полный контроль на тем сколько экземпляров мы создали, и в использовании Синглтона нет необходимости.
А вот это как раз неверно. На стадии конструирования нам надо определить, сколько экземпляров мы создали, и если для какого-то класса мы создаем один экземпляр, то это и есть паттерн Singleton.
Кажется у нас тут с Вами путаница в терминологии.
Похоже, Вы под Сингтоном подразумеваете единственный ЭКЗЕМПЛЯР ресурса.
Я же имею в виду СПОСОБ это обесепечить.
Похоже, Вы под Сингтоном подразумеваете единственный ЭКЗЕМПЛЯР ресурса.
Я же имею в виду СПОСОБ это обесепечить.
Именно поэтому я и начал с описания паттерна.
В описании сказано: «гарантируйте, что у класса один экземпляр». Все остальное — это реализации паттерна, которые могут быть разными.
В описании сказано: «гарантируйте, что у класса один экземпляр». Все остальное — это реализации паттерна, которые могут быть разными.
Вот она причина нашего недопонимания и как следствие спор о том, что чуше теплое или мягкое.
Каждый паттерн состоит из нескольких частей:
— Проблема, которую он решает
— Способ решить проблему архитектурно (На уровне ролей)
«гарантируйте, что у класса один экземпляр» — это не весь паттерн, это только проблема, которую решает паттерн.
Способ решения — «Сделайте класс Синглтон, который который контроллирует создание экземпляра и предоставляет доступ к этому экземпляра»
А детали реализации зависят от языка и необходимости в ленивой инициализации или в поддержке многопоточности. Именно детали описаны в топике.
Каждый паттерн состоит из нескольких частей:
— Проблема, которую он решает
— Способ решить проблему архитектурно (На уровне ролей)
«гарантируйте, что у класса один экземпляр» — это не весь паттерн, это только проблема, которую решает паттерн.
Способ решения — «Сделайте класс Синглтон, который который контроллирует создание экземпляра и предоставляет доступ к этому экземпляра»
А детали реализации зависят от языка и необходимости в ленивой инициализации или в поддержке многопоточности. Именно детали описаны в топике.
Способ решения — «Сделайте класс Синглтон, который который контроллирует создание экземпляра и предоставляет доступ к этому экземпляра»
У нас с вами разный способ решения. Я свой специально цитировал в самом начале беседы: «Ensure a class has only one instance, and provide a global point of access to it.»
У Вас ошибочное пониманимание того что является Паттерном.
«Ensure a class only has one instance, and provide a global point of access to it.» — это всего лишь секция «Intent» в описании патерна.
Цитата из GoF:
Из определения видно, что описание необходимых классов входит паттерн.
Согласно GoF, для реализации паттерна Singlton нужен всего один класс:
Тепрь цитирую Вас:
Единственный экземпляр класса в системе не является Синглтоном.
Если я создаю объект и через DI передаю его объектам — это не синглтон.
Синглтон — это паттерн, состоящий из вспомогательного класса, который предоставляет доступ к тому единственному экземпляру.
«Ensure a class only has one instance, and provide a global point of access to it.» — это всего лишь секция «Intent» в описании патерна.
Цитата из GoF:
In general, a pattern has four essential elements:
- The pattern name is a handle we can use to describe a design problem, its solutions, and consequences in a word or two.....
- The problem describes when to apply the pattern......
- The solution describes the elements that make up the design, their relationships, responsibilities, and collaborations. The solution doesn't describe a particular concrete design or implementation, because a pattern is like a template that can be applied in many different situations. Instead, the pattern provides an abstract description of a design problem and how a general arrangement of elements (classes and objects in our case) solves it.
- The consequences are the results and trade-offs of applying the pattern.....
Из определения видно, что описание необходимых классов входит паттерн.
Согласно GoF, для реализации паттерна Singlton нужен всего один класс:
Participants
- Singleton
- defines an Instance operation that lets clients access its unique instance. Instance is a class operation (that is, a class method in Smalltalk and a static member function in C++).
- may be responsible for creating its own unique instance.
Тепрь цитирую Вас:
А вот это как раз неверно. На стадии конструирования нам надо определить, сколько экземпляров мы создали, и если для какого-то класса мы создаем один экземпляр, то это и есть паттерн Singleton.
Единственный экземпляр класса в системе не является Синглтоном.
Если я создаю объект и через DI передаю его объектам — это не синглтон.
Синглтон — это паттерн, состоящий из вспомогательного класса, который предоставляет доступ к тому единственному экземпляру.
Тем, кто считает, что синглтон double-checked locking работает всегда и везде, рекомендую прочитать статью Рихтера/Александреску и убедиться в обратном
www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
Ура! Наконец-то и эту запостили!
Респект и уважуха!
Респект и уважуха!
Я не умею читать, или в этой статье нет ни слова про C#?
Кстати про C# — в статье упоминалось это habrahabr.ru/post/130318/ первоисточник по ссылкам искать лень.
Тип перестановки Перестановка разрешена
Загрузка-загрузка Да
Загрузка-запись Да
Запись-загрузка Да
Запись-запись Нет
То есть проблема перестановки, указанная в этой статье, в С# не имеет места.
Операции записи в поля класса конструктором не могут поменяться местами с записью в ссылочное поле instance.
Тип перестановки Перестановка разрешена
Загрузка-загрузка Да
Загрузка-запись Да
Запись-загрузка Да
Запись-запись Нет
То есть проблема перестановки, указанная в этой статье, в С# не имеет места.
Операции записи в поля класса конструктором не могут поменяться местами с записью в ссылочное поле instance.
Кстати, я полез и перечитал раздел Рихтера, на который вы ссылаетесь (29-я глава, раздел The Famous Double-Check Locking Technique).
Так вот, ваш пример
— это смесь из двух примеров Рихтера.
Первый выглядит вот так:
Обратите внимание на Monitor.* и Interlocked.Exchange.
А вот второй:
Обратите внимание отсутствие Monitor.* и Interlocked.CompareExchange.
А в вашем примере — одновременно и блокировка, и CompareExchange. Какой-то франкенштейн, вы не находите?
Ну и да, о варианте с CompareExchange Рихтер пишет, что его можно применять только тогда, когда конструктор не имеет побочных эффектов.
(ну и да, полезно понимать, что первая реализация, которая с Monitor.*, блокирует доступ навсегда, и реальный ее посыл в том, чтобы если нам не удалось создать синглтон, то приложение надо ронять полностью)
Так вот, ваш пример
if(instance != null) return instance;
Monitor.Enter(s_lock);
Singleton temp = new Singleton();
Interlocked.CompareExchange(ref instance, temp, null);
Monitor.Exit(s_lock);
return instance;
— это смесь из двух примеров Рихтера.
Первый выглядит вот так:
// If the Singleton was already created, just return it (this is fast)
if (s_value != null) return s_value;
Monitor.Enter(s_lock); // Not created, let 1 thread create it
if (s_value == null) {
// Still not created, create it
Singleton temp = new Singleton();
// Save the reference in s_value (see discussion for details)
Interlocked.Exchange(ref s_value, temp);
}
Monitor.Exit(s_lock);
// Return a reference to the one Singleton object
return s_value;
Обратите внимание на Monitor.* и Interlocked.Exchange.
А вот второй:
if (s_value != null) return s_value;
// Create a new Singleton and root it if another thread didn't do it first
Singleton temp = new Singleton();
Interlocked.CompareExchange(ref s_value, temp, null);
// If this thread lost, then the second Singleton object gets GC'd
return s_value; // Return reference to the single object
Обратите внимание отсутствие Monitor.* и Interlocked.CompareExchange.
А в вашем примере — одновременно и блокировка, и CompareExchange. Какой-то франкенштейн, вы не находите?
Ну и да, о варианте с CompareExchange Рихтер пишет, что его можно применять только тогда, когда конструктор не имеет побочных эффектов.
(ну и да, полезно понимать, что первая реализация, которая с Monitor.*, блокирует доступ навсегда, и реальный ее посыл в том, чтобы если нам не удалось создать синглтон, то приложение надо ронять полностью)
Спасибо, подправил :). Действительно, когда сверял код, то скопировал не ту строку.
Такими темпами и с таким обсуждением, чую, скоро у нас здесь ещё лучшая реализация появится.
Такими темпами и с таким обсуждением, чую, скоро у нас здесь ещё лучшая реализация появится.
double-checked singleton не работает на архитектурах, поддерживающих out-of-order execution. На этих архитектурах его можно реализовать, только с использованием memory barriers.
www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
А где-нибудь есть информация, какие именно это архитектуры? На x86, насколько я понимаю, этой проблемы нет.
А семантика volatile в .NET отличается от таковой, которая появилась в JMM с версии 1.5? Если да, то чем, можете пояснить? А если нет, то почему тогда не работает? Начиная с Java 5 проблемы более не существует.
Про С#/.NET не знаю. Под «не работает» — я имел в виду не работает double-checked singleton в каноническом виде, приведенном выше.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Три возраста паттерна Singleton