Допустимые глобальные переменные и предполагаемая экономия памяти.

Вот уже 20 лет я преподаю программирование в университете Буэнос-Айреса. На курсе программной инженерии мы изучаем паттерны проектирования, и одна и та же «схема» повторяется раз за разом, вызывая почти дежавю. Я убедился в этом на нескольких проектах и при обращении со свободным ПО, которым мне приходилось пользоваться:

Как «по волшебству» в коде возникает паттерн синглтон.

Источник зла

Этот паттерн применяется в отрасли десятилетиями. Его популярность связывают с отличной книгой «Паттерны объектно-ориентированного проектирования». Синглтон используется во множестве фреймворков, а в литературе редко встречаются рекомендации его избегать. Несмотря на это, в соответствующей статье Википедии находим предупреждение в стиле Данте:

«Критики синглтона рассматривают синглтон как антипаттерн, часто используемый в таких сценариях, где он скорее вреден».

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

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

Почему его не стоит использовать

1. Он нарушает принцип биекции

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

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

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

https://mcsee.hashnode.dev/the-one-and-only-software-design-principle

2. Он вызывает глобальную связность

Это глобальная ссылка. Опять же, согласно Википедии:

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

Априори это кажется преимуществом — ведь нам не приходится передавать информацию о контексте. Но на деле такой подход приводит к сильной связности. Ссылку на синглтон невозможно изменить ни по условиям среды (будь то среда разработки или производство), ни динамически. То есть, нельзя выстраивать стратегию, исходя из актуальной загруженности. Её нельзя заменить двойным тестом, а также нельзя внести изменения, поскольку они могут начать распространяться как рябь на воде.

https://mcsee.hashnode.dev/coupling-the-one-and-only-software-design-problem

3. В нём много говорится о (случайных) деталях реализации и мало о (существенной) зоне ответственности

Если как можно раньше сосредоточиться на проблемах реализации (ведь синглтон — это паттерн реализации), то мы сразу начинаем ориентироваться на случайные факторы (как) и недооцениваем самое важное, что есть в объекте: за что он отвечает.

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

<?
	

	class God {
	    private static $instance = null;
	

	    private function __construct() { }
	

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

4. Он не даёт писать хорошие модульные тесты

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

Нам приходится обязательно зависеть от глобального состояния, на которое ссылается синглтон.

5. Не экономится пространство в памяти

В пользу употребления синглтона часто приводится аргумент, что синглтон позволяет не конструировать множественные временные (volatile) объекты. Это мнимое преимущество нивелируется при работе с виртуальными машинами, где настроены эффективные механизмы сборки мусора.

В таких виртуальных машинах, применяемых в большинстве современных языков, оказывается гораздо затратнее хранить объекты в области памяти, которую алгоритм сборки мусора проходит дважды (mark & ​​sweep), чем создавать временные объекты, а затем быстро их удалять.

6. Синглтон не позволяет использовать внедрение зависимостей

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

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

7. Он нарушает соглашение о создании экземпляров

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

<?
	

	final class God extends Singleton {
	}
	

	$christianGod = new God();

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

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

<?
	

	class Singleton {
	    private function __construct() {
	        throw new Exception('Cannot Create new instances');
	    }
	}

8. Мы вынуждены явно связываться с реализацией

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

<?
	

	$christianGod = God::getInstance();
	// Почему мы должны быть в курсе о getInstance, когда создаем объект ?

9. Он мешает создавать автоматизированные тесты

Если мы будем придерживаться разработки через тестирование (TDD), то объекты будут определяться чисто и исключительно на основе их поведения. Следовательно, при создании программ по методологии TDD в них никоим образом не может возникнуть такой феномен как синглтон.

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

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

10. Уникальные концепции зависят от контекста

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

Структура паттерна в соответствии с определением, приводимым в книге «Паттерны проектирования»
Структура паттерна в соответствии с определением, приводимым в книге «Паттерны проектирования»
Класс (и вся модель) в биекции отсутствуют. Соответственно, все отношения, связанные с данным классом, будут недействительными
Класс (и вся модель) в биекции отсутствуют. Соответственно, все отношения, связанные с данным классом, будут недействительными

11. С ним сложно справиться в многопоточных окружениях

Может оказаться непросто реализовать этот паттерн в многопоточных программах. Если два потока выполнения) одновременно пытаются создать пока не существующий экземпляр, то только один из них в итоге должен успешно сформировать объект. Классически данная проблема решается при помощи взаимного исключения в методе создания класса, реализующего метод. Так обеспечивается возможность многократного входа в него.

12. Накапливается мусор, занимающий место в памяти

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

13. Такое состояние с накоплением мусора недопустимо при применении модульных тестов

Персистентное состояние — враг модульных тестов. Модульные тесты эффективны не в последнюю очередь потому, что каждый тест должен быть  независим от всех остальных. Если это не так, то результаты тестирования могут зависеть от того, в каком порядке выполняются тесты, и эти тесты становятся недетерминированными. В результате может случаться так, что тесты не проходят в ситуациях, когда должны проходить. Ещё хуже, если тесты проходят только в том порядке, в котором они выполнялись. Такая ситуация может маскировать ошибки, а это очень плохо.

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

14. Если налагается ограничение на создание новых объектов, то нарушается принцип единственной ответственности.

Принцип единственной ответственности класса заключается в создании экземпляров. Если наделить класс любой другой ответственностью, это нарушит принцип единственной ответственности. Класс не должно волновать, является ли он синглтоном. Всё, что он должен делать — строго соблюдать правила бизнес-логики. Если существует потребность, чтобы некоторые экземпляры были уникальными, то за это будет отвечать третий объект-посредник, например, Фабрика или Строитель.

15. За обладание глобальной ссылкой мы расплачиваемся не только связностью

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

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

16. Вероятно, он просто проник сюда за компанию

Многими синглтонами как таковыми злоупотребляют, превращая их в глобальные репозитории ссылок

Очень велик соблазн использовать синглтон как точку входа для новых ссылок.

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

Мало того, что синглтон — корень всех зол, так он ещё и легко проникает в код за компанию. В больших проектах в синглтоне просто накапливается мусор — чтобы не мешал.

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

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

Известно несколько примеров анализа первопричин (Root Cause Analysis) и постмортемов, демонстрирующих, как дефекты продукции коррелируют с состоянием систем и х недотестированностью.

https://blog.ndepend.com/singleton-design-pattern-impact-quantified/

18. Это запах кода

https://maximilianocontieri.com/code-smell-32-singletons

Для чего может понадобиться синглтон

Сформулировав аргументы против использования синглтона, давайте попробуем рассмотреть его достоинства:

1. Этот паттерн помогает экономить память

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

2. Он хорош для моделирования уникальных объектов

При помощи синглтона можно гарантировать уникальность некоторой концепции. Но это не единственный и не лучший способ. Давайте перепишем предыдущий пример:

<?
	

	interface Religion {
	    // Определим общее для всех религий поведение
	}
	

	final class God {
	    // Для различных религий характерны разные убеждения
	}
	

	final class PolythiesticReligion implements Religion {
	    private $gods;
	

	    public function __construct(Collection $gods) {
	        $this->gods = $gods;
	    }
	}
	

	final class MonotheisticReligion implements Religion {
	    private $godInstance;
	

	    public function __construct(God $onlyGod) {
	        $this->godInstance = $onlyGod;
	    }
	}
	

	// Согласно христианству и некоторым другим религиям,
	// существует всего один Бог.
	// Это не соблюдается в других религиях.
	

	$christianGod = new God();
	$christianReligion = new MonotheisticReligion($christianGod);
	// В таком контексте Бог уникален
	// Невозможно создать нового Бога или изменить имеющегося
	// Это сущность с глобальной областью действия
	

	$jupiter = new God();
	$saturn = new God();
	$mythogicalReligion = new PolythiesticReligion([$jupiter, $saturn]);
	

	// Боги могут быть уникальными (или нет) в зависимости от контекста
	// Можно создавать тестовые религии, обладающие либо не обладающие показателем уникальности Бога
	// Этот код менее тесно связан, 
	// поскольку мы разрываем прямую ссылку на класс God 
	// Единственная ответственность класса God — создавать богов 
	// Но не управлять ими

Создание единственного экземпляра и управление им не связаны. Уникальность творения осуществляется в одной контролируемой точке, ссылки на классы от неё откреплены.

3. Синглтон позволяет не повторять затратные инициализации

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

Если нам понадобится единственная точка управления или кэш, то у нас будет доступ к известному объекту, связанному с известным контекстом. Кроме того, такой объект должен быть легко заменим в соответствии с условиями среды, тестовой конфигурации  и т.д. Определённо, для этого найдутся варианты получше, чем синглтон.

Решение

Существует ряд приёмов, позволяющих постепенно вывести синглтоны из употребления и не злоупотреблять ими. Некоторые из них перечислены в следующей статье:

https://mcsee.hashnode.dev/how-to-decouple-a-legacy-system

Заключение

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

P.S. Обращаем ваше внимание на то, что у нас на сайте проходит распродажа.

Комментарии (109)


  1. AstarothAst
    17.01.2025 12:52

    Разработчики на Spring:

    - Синглтон зло? Ух ты!


    1. Tishka17
      17.01.2025 12:52

      Тут важно не путать скоуп "синглтон" в IoC-контейнерах и синглтон как паттерн. Статья о втором.


  1. php7
    17.01.2025 12:52

    Автор теоретик?


    1. piton_nsk
      17.01.2025 12:52

      Вот уже 20 лет я преподаю программирование

      Он самый.


    1. ImagineTables
      17.01.2025 12:52

      Всё, что вы хотели сказать ему, вы можете сказать мне и моему другу «Смит-Вессону». clint_eastwood.jpg

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

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

      А затем они идут и делают функцию, которая возвращает корневой интерфейс Direct3D. Интерфейсы COM, как вы знаете, надо запрашивать через ::CoCreateInstance() в любых потребных количествах, но… На дворе 1996 год. И как, спрашивается, вы себе это представляете — целых ДЖВА 3D-ускорителя в одном компьютере?! Только пять самых богатых корпораций в мире могут позволить себе два ускорителя ценой в автомобиль, и все они уже купили себе рабочие станции от Silicon Graphics! Поэтому нет, держите вместо этого глобальную функцию.

      Две кнопки “Start”? Конечно же, такое может присниться только в кошмарном сне. Как и два рабочих стола.

      Два footer'а на одной веб-странице? No wai. Ловите извращенца.

      В общем, к счастью для них, их говноподелия (3D API и операционные системы) обычно столько не живут, чтобы они столкнулись с подросшим синглтоном, который ставит жирный крест на развитии системы. А уж про говносайтики что говорить — они как бабочки-однодневки, умирают каждый день.

      Но к несчастью для нас, синглтонный подход («что-то какого-то типа может/должно быть только одно») прописался во многих стандартах, и живее всех живых. Например, HTML использует привязку по идентификаторам в куче подстандартов (один из них — WAI ARIA для поддержки незрячих юзеров), что не даёт шаблонизировать разметку без такой-то матери.


      1. mayorovp
        17.01.2025 12:52

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


        1. ImagineTables
          17.01.2025 12:52

          Понимаю, о чём вы, но это чисто техническое ограничение, для обхода которого придумали паттерн two stage creation. Если помните, так же были устроены многие обёртки над WinAPI. Нет никакой проблемы сделать метод Init(), или Create(), который принимал бы все нужные параметры.


          1. mayorovp
            17.01.2025 12:52

            Но зачем?.. Чем двухэтапное создание лучше простой функции? Единственное что я вижу - это возможность включить конструктор в TLB.


            1. Tishka17
              17.01.2025 12:52

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


              1. mayorovp
                17.01.2025 12:52

                И чем же вызов функции так сложен?..


                1. Tishka17
                  17.01.2025 12:52

                  Вызов функции не сложен. Но двухфазная инициализация используется обычно в тех случаях когда ты не можешь две фазы склеить в одну: то есть между вызовами будет что-то ещё происходить. И вот это "что-то ещё" открывает дорогу багам


                  1. mayorovp
                    17.01.2025 12:52

                    Так если вызов функции не сложен, то зачем менять его на двухфазную инициализацию, открывающую дорогу багам?


            1. ImagineTables
              17.01.2025 12:52

              Но зачем?.. Чем двухэтапное создание лучше простой функции?

              Так проблема-то не в функции, а в том, что нельзя было создать два директикса одновременно, чтобы управлять двумя картами сразу. Синглтон.


              1. mayorovp
                17.01.2025 12:52

                Ну так я о том и говорю, проблема вообще не в функции и не в CoCreateInstance.


                1. ImagineTables
                  17.01.2025 12:52

                  Проблема в синглтоне, как таковом, а то, что они нарушили собственные правила, запретив инстанцирование встроенными средствами — характерный симптом этой проблемы.


                  1. mayorovp
                    17.01.2025 12:52

                    Так всё-таки, проблема в синглтоне или в способе инстанцирования?
                    Если бы вы использовали CoCreateInstance, но не могли создать второй корневой объект - вам бы было легче?


  1. Revertis
    17.01.2025 12:52

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


    1. Tishka17
      17.01.2025 12:52

      А можно пример реально нужного случая? Тот, который по другому не решить (например через DI единственного экземпляра)


      1. Revertis
        17.01.2025 12:52

        Например, класс, загружающий картинки/иконки, содержащий кэш. В любом случае этот объект будет один, и создавать их несколько вредно. (Только если по видам картинок, если вдруг надо.)

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

        Или некий класс в P2P-мессенджере, держащий соединения с другими нодами.

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

        И проблемы с инициализацией из разных потоков, например, можно обойти, создав такой объект на раннем этапе, до старта дополнительных потоков...


        1. Tishka17
          17.01.2025 12:52

          Ну давайте подумаем.

          1. Хотите ли в тестах продолжнать использовать уже загрязненный кэш вместо создания нового объекта?

          2. А не появится ли необходимость запустить два логических экземпляра объекта в одном процессе, чтобы у них были свои кэши? (3 года назад ботоделы меня убеждали что не нужно такое никогда, а потом у половины из них "мультиботы" в ходу оказались)

          Про пул соединений тоже классика - сгодня он один, а завтра завезли CQRS и у нас два пула: на чтение и на запись.

          Но я отвлекся, вопрос был: чем синглтон в этих случаях ЛУЧШЕ других подходов, а не почему он может прокатить в частном случае


          1. boldape
            17.01.2025 12:52

            Ответ очевиден, синглтон лучше тем, что не требует передачи контекста/себя. Вот вы сосласиь на DI, а разве инжекция не требует того самого ну этого как же его, а вот синглтона?

            Есть способы обойти ВСЕ перечисленные проблемы и остаться синглтоном, ну собственно DI это и подтверждает, но можно и самому сделать если надо.

            Вот когда появится потребность мульти ботов или сикуреэс, вот тогда и перепишем с синглтона на 2(М) синглтонов просто разного типа. А с тестами все не просто, а элементарно - сделайте уже наконец функцию резет вашему синглтону или ещё лучше реализуейте сэтабл синглтон и тогда вот это поворот можно даже хоть мокать хоть фэйкать его как угодно.

            А синглтонов в коде полно, например ОС апи - синглтон хоть и явно никакого объекта не требует, логирование, стандартные консольные потоки ввода/вывода, ФС, реестр и т.д.

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


      1. NeoNN
        17.01.2025 12:52

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


        1. mayorovp
          17.01.2025 12:52

          классический паттерн (даже потокобезопасный) не очень широко применяется сейчас

          И почему же он не очень широко применяется? А потому что он - зло. Статья именно про него.


          1. NeoNN
            17.01.2025 12:52

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


          1. piton_nsk
            17.01.2025 12:52

            И почему же он не очень широко применяется?

            Потому что мало где нужен, да и сейчас повсеместно DI контейнеры, пишешь что-то вроде container.AddSingleton<MySingletonService>, а вся возня за тебя уже сделана.

            Я работал на проектах где были те самые настоящие синглтоны, с проверкой на нулл и локом, проблем не было вообще никаких.


            1. mayorovp
              17.01.2025 12:52

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


  1. GospodinKolhoznik
    17.01.2025 12:52

    Дети, запомните, синглтон это плохо. Пнятненько?


  1. ProgerMan
    17.01.2025 12:52

    Мне показалось, или все беды в мире от синглтонов?
    Глобальное потепление, получается, тоже своего рода синглтон?


  1. flancer
    17.01.2025 12:52

    Вот за что мне симпатично функциональное программирование, так это за то, что там все функции - синглтоны. Раз у них нет состояния, то на всё приложение нужна ровно одна функция, выполняющая конкретную работу. И никто её демоном не считает.

    Ну и даёт Maximiliano Contieri жару в универе Буэнос-Айреса!!


    1. Nikita22007
      17.01.2025 12:52

      Мне кажется, что чистые функции и синглтон это всё-таки разные понятия


      1. flancer
        17.01.2025 12:52

        Вы абсолютно правы!


    1. Tishka17
      17.01.2025 12:52

      Функции не синглтоны, потому что существуют замыкания.


      1. flancer
        17.01.2025 12:52

        А вот вы не правы. Бывает, что функции существуют и без замыканий. Например, в C.


        1. Tishka17
          17.01.2025 12:52

          мы всё ещё про функциональное программирование говорим?


          1. flancer
            17.01.2025 12:52

            Да.


            1. Tishka17
              17.01.2025 12:52

              на си? без замыканий?


              1. flancer
                17.01.2025 12:52

                Да. А что вас смущает?


                1. Tishka17
                  17.01.2025 12:52

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


                  1. flancer
                    17.01.2025 12:52

                    Ну так вы сами не изменяйте переменные - и все дела. ФП - это прежде всего стиль написания кода (неизменяемость данных и минимизация побочных эффектов). То, что Си не очень подходит для ФП, никак не аргументирует вашу реплику "Функции не синглтоны, потому что существуют замыкания". Функции не синглтоны не поэтому. Как говорится, "слово - не воробей, да и вообще ничего не воробей, кроме воробья" (с)

                    Автор оригинального поста, Maximiliano Contieri, говорит про синглтон в контексте паттернов проектирования ООП, а так-то у термина "синглтон" значений слегка больше, чем одно. Например, жизненный цикл зависимостей в DI контейнерах тоже может быть синглтоном. Поэтому утверждать "Синглтон - корень всех зол!" несколько кликбейтно. А если вам внимание важнее понимания, то вот и получите. Он про синглтон, и я про синглтон. Может у него в ООП синглтон и корень всех зол, но в ФП вообще такого понятия нет. Вернее, там все функции - одиночки. Нужны ровно в одном экземпляре.


                    1. Tishka17
                      17.01.2025 12:52

                      не изменяйте переменные и все дела"

                      Ну давайте решим простую задачу в функциональном стиле: пользователь вводит число N и потом N чисел, надо их отсортировать и вывети. Как это реализовать на Си без любых изменений переменных? Сортировку можете не писать, давайте её держать в голове как готовую (функция принимает массив, возвращает массив), ограничимся только вводом данных. Как это будет выглядеть на си в ФП стиле?

                      жизненный цикл зависимостей в DI контейнерах тоже может быть синглтоном. 

                      Он так называется по тем же причинам что и паттерн синглтон, но это принципиально разные вещи. Вы возможно не поняли, но статья про паттерн синглтон, а НЕ про возможности DI-контейнеров

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


                      1. BioHazzardt
                        17.01.2025 12:52

                        Ну давайте решим простую задачу в функциональном стиле: пользователь вводит число N и потом N чисел, надо их отсортировать и вывети. Как это реализовать на Си без любых изменений переменных? Сортировку можете не писать, давайте её держать в голове как готовую (функция принимает массив, возвращает массив), ограничимся только вводом данных. Как это будет выглядеть на си в ФП стиле?

                        так изи же

                        char* transform(char* in, int len) {
                           char* result = malloc(sizeof(char) * (size_t)len);
                           for (int i = 0; i < len; i++) {
                               result[i] = do_something(in[i]);
                           }
                        
                           return result;
                        }

                        никаких изменений глобального состояния нет. Зато памяти жрет больше


                      1. Tishka17
                        17.01.2025 12:52

                        вижу изменение: `result[i]=do_something`. Пожалуйста, продемонстрируйте код, где не будет изменяемых переменных


                      1. BioHazzardt
                        17.01.2025 12:52

                        поправка: тут меняется не сама переменная, а область памяти, на которую она указывает + смещение. В целом можно сделать через malloc/memcpy аллокацию нового массива и копирование памяти в него с добавлением элемента, но никто в здравом уме и твердой памяти так делать не будет, ибо это грозит стрельбой себе в колено из пулемета


                      1. Tishka17
                        17.01.2025 12:52

                        не важно, что именно меняется. в ФП изменяемые объекты/переменные/сущности/области памяти не существуют. Реализуйте пожалуйста БЕЗ изменений чего либо, чтобы показать что на Си можно писать в ФП стиле


                      1. BioHazzardt
                        17.01.2025 12:52

                        ну кок

                        char* transform_inner(char* src, size_t max, char *tmp, size_t len) {
                            char* result = malloc(sizeof(char) * len+1);
                            if (tmp != NULL) {
                              memset(result, tmp, len);
                              free(tmp);
                            }
                        
                            result[len] = do_something(src[len]);
                            if (len == max-1) 
                              return result;
                        
                            return transform_inner(src, max, result, len+1);
                        }
                        
                        char * transform(char * orig, int len) {
                            return transform_inner(orig, len, NULL, 0)
                        }

                        надеюсь не ошибся нигде, и про строчки с 3 по 8 можете не писать - ваш ФП-язык делает это под капотом, только free(tmp) делает сборщик мусора

                        з.ы. можно было бы конечно еще на тему ленивых вычислений в СИ поразмышлять, но мне лень


                      1. Tishka17
                        17.01.2025 12:52

                        все ещё вижу `result[len]=...` - модификация уже созданной выше структуры. (Я бы ещё и на free(tmp) поворчал - мы так меняем "состояние" блока памяти, а он мог где-то использоваться). Пожалуйста, напишите код так, чтобы после первоначальной инициализации с переменной больше ничего не происходило, никак её память не менялась вами в коде, иначе это не ФП код.

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


                      1. BioHazzardt
                        17.01.2025 12:52

                        еще раз. Ваш ФП-язык это делает под капотом. Нельзя одновременно аллоцировать память и писать в нее, эти операции всегда будут идти последовательно. Просто ваш ФП-язык делает это неявно, а ввиду низкоуровневости сишки эти подкапотные вещи надо писать руками. Я могу написать вот так:

                        // Подкапотная функция добавления в массив,
                        // которая вшита в компилятор/интерпретатор вашего
                        // ФП-языка. Реализация там будет чуть отличаться
                        // но суть та же самая
                        // - аллокация памяти
                        // - копирование оригинального массива
                        // - запись добавленного элемента
                        char* array_push_internal(char* arr, size_t len, char new_lem) {
                          char* result = malloc(sizeof(char) * len+1);
                            if (tmp != NULL) {
                              memset(result, tmp, len);
                              free(tmp);
                            }
                        
                            result[len] = new_elem;
                            return result;
                        }
                        
                        char* transform_inner(char* src, size_t max, char *tmp, size_t len) {
                            char* result = array_push(tmp, len, do_something(src[len]));
                            if (len == max-1) 
                              return result;
                        
                            return transform_inner(src, max, result, len+1);
                        }
                        
                        char * transform(char * orig, int len) {
                            return transform_inner(orig, len, NULL, 0)
                        }


                      1. Tishka17
                        17.01.2025 12:52

                        Именно что Под капотом. Речь шла не о том, чтобы реализовать на си код, идентичный результату компиляции ФП кода, а о том чтобы писать в ФП стиле. Ваша подкапотная магия некорректна, так как не контролирует сейчас наличие указателей на tmp. Боюсь чтобы в ФП стиле писать на Си, надо ОЧЕНЬ много ещё реализовать, а потом желательно ещё обвеситься линтерами которые будут проверять что вы не используете стандартные функции си вне этих мест


                      1. BioHazzardt
                        17.01.2025 12:52

                        если добавить ФП в вашем понимании в СИ - это будет не СИ, а что-то другое. Фишка си как раз в его низкоуровневости. Вы еще на ассемблере предложите писать в ФП-стиле, во хохма-то будет

                        З.Ы. а вообще можно сделать и так, если функцию вставки в массив написать на ассемблере или на том же си и прилинковать отдельно. Тогда у вас будет ваш ФП-стиль, добавление же под капотом будет работать


                      1. Tishka17
                        17.01.2025 12:52

                        Я не предлагал писать на Си в ФП стиле. Мне это как раз кажется сомнительной идеей - нужно переизобрести инстурментарий и запретить стандартный. Но почему-то человек выше сделал вид, что все нормально.


                      1. BioHazzardt
                        17.01.2025 12:52

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

                        путаетесь в показаниях, уважаемый


                      1. flancer
                        17.01.2025 12:52

                        Человек выше на Си писал последний раз лет 15-20 назад. Если вам это действительно интересно, можете поговорить с ChatGPT - https://chatgpt.com/share/678c07b7-e930-800d-92c5-e92d9d800daa Он там что-то такое выдал и умно рассуждает. Можете его переубедить.

                        "Функции не синглтоны, потому что существуют замыкания." (с)

                        Мне кажется, что вы пытались донести до меня мысль "В ФП не все функции синглтоны, потому что существуют замыкания".

                        Вот если бы вы свою мысль оформили таким образом, я бы, возможно, и не триггернулся. А если бы автор поста не назвал его "Синглтон - корень всех зол", я бы и на статью не триггернулся. Ну, как есть.

                        А так-то я ни в ФП ничего не понимаю, ни в программировании на С. Высказал свою точку зрения, получил фидбек. Всё норм - это же Хабр. За этим я и здесь.


                      1. BioHazzardt
                        17.01.2025 12:52

                        Человек выше на Си писал последний раз лет 15-20 назад.

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


                      1. flancer
                        17.01.2025 12:52

                        Вот вариант для массивов от ИИ. Я на Си не думаю, поэтому не могу сказать, что там лажа, а что нет.

                        код для массивов от ChatGPT
                        #include <stdio.h>
                        #include <stdlib.h>
                        
                        /*
                         * Рекурсивная копия массива:
                         * copyArray(src, n, dst, offset) копирует n элементов из src в dst,
                         * начиная с dst[offset], не меняя src.
                         */
                        void copyArray(const int *src, int n, int *dst, int offset) {
                            if (n == 0) {
                                return;
                            }
                            dst[offset] = src[0];
                            copyArray(src + 1, n - 1, dst, offset + 1);
                        }
                        
                        /*
                         * insertIntoSortedArray(a, n, x) возвращает указатель на *новый* массив
                         * длины n+1, куда вставлено число x в нужное место, предполагая что
                         * исходный массив a (длины n) уже отсортирован. Старый массив a не меняется.
                         */
                        int* insertIntoSortedArray(const int *a, int n, int x) {
                            if (n == 0) {
                                // Новый массив из одного элемента
                                int *res = malloc(sizeof(int));
                                res[0] = x;
                                return res;
                            }
                            if (x < a[0]) {
                                // x становится в начало нового массива, дальше копируем a
                                int *res = malloc((n + 1) * sizeof(int));
                                res[0] = x;
                                copyArray(a, n, res, 1);  // старый массив копируется со сдвигом
                                return res;
                            } else {
                                // Первый элемент нового массива совпадает со старым a[0],
                                // а x вставляем рекурсивно в "хвост" (a+1)
                                int *res = malloc((n + 1) * sizeof(int));
                                res[0] = a[0];
                                int *temp = insertIntoSortedArray(a + 1, n - 1, x);
                                copyArray(temp, n, res, 1);
                                free(temp);
                                return res;
                            }
                        }
                        
                        /*
                         * readSortedArray(n) считывает n чисел и возвращает указатель на
                         * отсортированный массив длины n. Реализовано рекурсивно:
                         * - если n==0, возвращаем NULL (пустой массив);
                         * - иначе читаем одно число x, рекурсивно получаем массив из (n-1) чисел
                         *   (уже отсортированный) и вставляем в него x, возвращая новый массив.
                         */
                        int* readSortedArray(int n) {
                            if (n == 0) {
                                return NULL;
                            }
                            int x;
                            scanf("%d", &x);
                            // Рекурсивно получаем отсортированный массив из n-1 элементов
                            int *arr = readSortedArray(n - 1);
                            // Вставляем новое число x в нужное место
                            int *res = insertIntoSortedArray(arr, n - 1, x);
                            free(arr); // старый (короче на 1) уже не нужен
                            return res;
                        }
                        
                        /*
                         * Рекурсивный вывод массива:
                         * printArray(a, n) печатает n чисел массива a.
                         */
                        void printArray(const int *a, int n) {
                            if (n == 0) {
                                return;
                            }
                            printf("%d ", a[0]);
                            printArray(a + 1, n - 1);
                        }
                        
                        int main(void) {
                            int n;
                            scanf("%d", &n);
                        
                            // Считываем n чисел и получаем новый (отсортированный) массив
                            int *sorted = readSortedArray(n);
                        
                            // Печатаем итог
                            printArray(sorted, n);
                            printf("\n");
                        
                            // Не забудем освободить итоговый массив
                            free(sorted);
                        
                            return 0;
                        }
                        

                        Просто человек выше очень сильно намекает, что на С нельзя написать что-то в функциональном стиле из-за того, что "у си немного так проблемы" с неизменяемыми переменными, да "и гарантий оптимизации хвостовой рекурсии тоже нет". А ChatGPT говорит:

                        С теоретической точки зрения, оба языка (Haskell и C) тьюринг-полны. Это значит, что любую задачу, формально решаемую на одном языке, в принципе можно решить и на другом. Соответственно, нет «таких программ, которые можно написать на Haskell и невозможно написать на C».

                        Возможно, что ChatGPT неправ, а возможно, что человек выше думал написать одно, а написал другое. А возможно и я чего-то там не так понял. Например то, что он таким образом опроверг мой тезис "Бывает, что функции существуют и без замыканий. Например, в C" Я-то точно помню, лет 15-20 назад функции в С были, а замыканий не было. Если что-то за это время поменялось и теперь в Си функции без замыканий не существуют - ребята, я просто не успел за прогрессом!!


                      1. mayorovp
                        17.01.2025 12:52

                        Вам ИИ привёл типичный процедурный код, а не функциональный. Не обманывайтесь похоже звучащими словами: то что в Си процедуры называются функциями никак не превращает процедурное программирование в ФП.


                      1. flancer
                        17.01.2025 12:52

                        Меня давным-давно учили, что процедура от функции отличается только тем, что функция возвращает результат, а процедура - нет. Но это было лет 30 назад и на Pascal'е.

                        Т.е., вы считаете, что функциональное программирование возможно только на специально созданных для этого языках? Что на Си нельзя писать в парадигме ФП? Я уже говорил, что я в ФП не очень глубоко погружался (у меня с абстрактным мышлением проблемы, а ФП - это математика, а математика - это абстрации), но у меня сложилось впечатление, что для использования ФП не нужны "специальные ЯП". И тут вы заявляете, что в Си нет функций, там только процедуры, а процедурами в ФП нельзя. Я в сомнениях, вы рушите мою картину мира.


                      1. mayorovp
                        17.01.2025 12:52

                        Функция от процедуры и правда отличается только возвращаемым значением, а вот функциональное и процедурное программирование - это две разные парадигмы.

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

                        Так что да, на Си нельзя писать в функциональной парадигме. Сильно мешает отсутствие замыканий и сборщика мусора.


                      1. flancer
                        17.01.2025 12:52

                        Что в этом коде на Си указывает на то, что он именно процедурный, и в каком месте он не соответствует функциональной парадигме?

                        int add(int a, int b) {
                            return a + b;
                        }

                        Да, это простой пример. Это очень простой пример. И для очень простого примера должен быть очень простой ответ на мой вопрос.


                      1. mayorovp
                        17.01.2025 12:52

                        В этом - ничего, он слишком простой.


                      1. flancer
                        17.01.2025 12:52

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

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


              1. BioHazzardt
                17.01.2025 12:52

                в си вроде как замыканий нет, но в 11 стандарте, если склероз не изменяет, добавили указатели на функции, но это все равно не замыкание в полном смысле этого слова


  1. keekkenen
    17.01.2025 12:52

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

    после этой фразы можно дальше не читать

    наверное, вы просто не знаете чего хотите или рандомно решаете, что будет синглтоном, а что нет..


  1. NeoNN
    17.01.2025 12:52

    В анемичной модели с сервисами зачастую подвязанные через DI сервисы - это определенные как single instance сущности, и никакого смысла их делать чем-то иным нет.


    1. pes_loxmaty
      17.01.2025 12:52

      Всё так, поскольку сервисы не имеют своего стейта. Это по сути молотилка в которую с одной стороны загружают данные, с другой получают другие данные. Автор же похоже рассуждает об этих данных, которые могут быть представлены объектами ( объекты данных? или как их назвать?). У них конечно есть стейт и в общем случае каждый из них уникален и конечно никакие они не синглтоны.


  1. gelioson
    17.01.2025 12:52

    Синглтон — корень всех зол

    Только Ситхи все возводят в абсолют


  1. BugM
    17.01.2025 12:52

    Spring и все что он принес в массы вообще мимо вас прошло?


    1. Anarchist
      17.01.2025 12:52

      Там вроде php. Он плохо влияет на неокрепшую психику.


      1. ddruganov
        17.01.2025 12:52

        То ли дело джава, да?)


    1. mayorovp
      17.01.2025 12:52

      А разве Spring использует порождающий паттерн "Одиночка"?


  1. DirectoriX
    17.01.2025 12:52

    Почему не стоит использовать:

    5. Не экономится пространство в памяти

    12. Накапливается мусор, занимающий место в памяти

    Когда может пригодиться:

    1. Этот паттерн помогает экономить память

    Это ещё корпускулярно-волновой дуализм, или уже диссоциативное расстройство личности (синглтона)?


    1. Nikita22007
      17.01.2025 12:52

      Прочтите, пожалуйста, чуть дальше заголовка:

      1. Этот паттерн помогает экономить память

      Этот аргумент не выдерживает критики

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


  1. SergioPredatore
    17.01.2025 12:52

    MemoryCache в C# является синглтоном, прекрасно внедряется через DI и прекрасно контролируется в тестах. И как им нормально пользоваться если бы он не был синглтоном, я не очень представляю.


    1. Tishka17
      17.01.2025 12:52

      А в чем профит того что он синглторн если он везде внедряется через DI? В чем отличие от не-синглтона?


    1. mvv-rus
      17.01.2025 12:52

      И как им нормально пользоваться если бы он не был синглтоном, я не очень представляю.

      Просто создаете его вызовом new MemoryCache(...), сохраняете ссылку на него (например, в поле своего объекта, реализующего какой-то другой сервис с временем жизни Singleton), и пользуетесь. Это разумно делать там, где нужны другие свойства кэша, такие, как автоматическое удаление устаревших объектов. Преимущество своего кэша - в том, что его можно создать с другими параметрами, например - с автосжатием по достижении определенного размера. Иногда, хоть этот размер и меряется в попугаях, это полезно. С общим же для приложения кэшем - реализацией singleton-сервиса IMemoryCache - такое делать нельзя, потому что для использования автосжатия надо указывать, сколько попугаев в каждом размещаемом в кэше объекте, а другие пользователи общего кэша этим не заморачиваются.


  1. MountainGoat
    17.01.2025 12:52

    Читал я код от автора, начитавшегося такой фигни. У него нет синглтона. У него есть объект, создающийся в main() и потом указатель на этот объект входит в состав 4/5 всех классов программе и передаётся в каждый конструктор.


    1. Tishka17
      17.01.2025 12:52

      А проблема в чем?


      1. MountainGoat
        17.01.2025 12:52

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


        1. shushara4241
          17.01.2025 12:52

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

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


        1. mayorovp
          17.01.2025 12:52

          Зато теперь этот объект можно нормально сконфигурировать перед использованием, больше нету гонки между конфигурацией и использованием.


        1. Tishka17
          17.01.2025 12:52

          Простите, но между "вручную копировать" и "вручную искать где же юзается get_instance раньше чем надо" я выбираю первое. Это точно проблема?


          1. MountainGoat
            17.01.2025 12:52

            Я выбираю сразу написать по моему главному правилу: невалидное состояние системы непредставимо в системе типов. Если синглтон надо конфигурировать, и это нельзя просто сделать в самом get_instance, то get_instance будет возвращать объект АМожетБытьСинглтон, который надо будет проверить на фактический тип, чтобы получить доступ к возможностям синглтона. (Как именно и какие накладные расходы - уже зависит от языка).


            1. Tishka17
              17.01.2025 12:52

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


  1. Bardakan
    17.01.2025 12:52

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

    Если вы знаете, что синглтон вам больше не нужен, то что мешает добавить в него метод, обнуляющий ресурсы?


  1. CrashLogger
    17.01.2025 12:52

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


    1. mayorovp
      17.01.2025 12:52

      Точно одно соединение к БД? А если на сервер пришло несколько запросов одновременно - они там за это соединение не подерутся?


    1. Tishka17
      17.01.2025 12:52

      Не просто НЕ один коннект, но регулярно 2 пула коннектов появляется (на мастер и на реплики)


  1. xentoo
    17.01.2025 12:52

    А в чем проблема сделать тестовый friend класс для загрузки testdouble?


  1. BioHazzardt
    17.01.2025 12:52

    Этот аргумент не выдерживает критики

    По-моему статья не выдерживает критики, синглтоны нужны (тот же пул коннектов с БД нам что, при обработке запроса каждый раз заново создавать? Да и что автор подразумевает под синглтоном?)

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


    1. mayorovp
      17.01.2025 12:52

      Да и что автор подразумевает под синглтоном?)

      Общеизвестный порождающий паттерн, что же ещё он мог подразумевать?


      1. BioHazzardt
        17.01.2025 12:52

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

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

        З.Ы. ну и говоря про частные случаи - если сервис по некоторым причинам может использовать только in-memory кэш, то такой кэш не сможет быть не синглтоном - иначе он просто работать не будет


        1. Tishka17
          17.01.2025 12:52

          Не обязательно будет. Я уже приводил пример необходимости двух пулов: для чтения (в реплики) и для записи (в мастер)


          1. BioHazzardt
            17.01.2025 12:52

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


            1. Tishka17
              17.01.2025 12:52

              Синглтон это когда один экземпляр. Почему у вас два?

              Усложним, у нас приложение стало модульным монолитом (мы пока не хотим микросервисы, но готовимся). В каждом ограниченном контексте тоже есть пул соединений. Уже стало и не два, выходит.


              1. BioHazzardt
                17.01.2025 12:52

                type DbSingleton struct {
                  rpool *mydriver.ConnPool
                  wpool *mydriver.ConnPool
                }
                
                func (s *DbSingleton) Reader() *mydriver.ConnPool {
                  return s.rpool
                }
                
                func (s *DbSingleton) Writer() *mydriver.ConnPool [
                  return s.wpool
                ]

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


                1. Tishka17
                  17.01.2025 12:52

                  Так погодите, выше утверждалось, что пул - синглтон, а теперь он как раз и не синглтон.


                  1. BioHazzardt
                    17.01.2025 12:52

                    не надо демагогии, я писал про пул, работающий в обе стороны. Если вам нужно два пула с разделением чтения и записи ничего не мешает завернуть из в синглтон, показанный выше. Это во-первых. А во-вторых, если пулы обращаются к разным репликам (а иначе нахрена разделение делать), то тут они хоть и будут иметь один тип, но функционально это будут два отдельных объекта


                    1. Tishka17
                      17.01.2025 12:52

                      Если для вас слова "то что считалось синглтоном перестало им быть" - демагогия, но я не знаю как ещё донести мысль. Было исходное утверждение "Тип ХХХ - синглтон", в процессе обсуждения мы таки выяснили, что нужны 2 его экземпляра (или больше). То есть исходное предположение было неверно. С синглтонами всегда так: думаешь что это железобетонно одна штука, а потом выясняется что иногда и нет. И сиди бегай все get_instance() ищи.

                      то тут они хоть и будут иметь один тип, но функционально это будут два отдельных объекта

                      вот эту часть не понял. Паттерн синглтон именно про отсутвтвие нескольких экземпляров одного типа


                      1. BioHazzardt
                        17.01.2025 12:52

                        а потом выясняется что иногда и нет.

                        Если такое выясняется раз в неделю, то у вас что-то не так проектированием архитектуры, и отсутствие или наличие синглтонов там вряд ли поможет


                      1. Tishka17
                        17.01.2025 12:52

                        Нет, это выясняется раз в год (когда уже всё обрастает мясом и использванием его). И ты сидишь и тратишь недели на попытки избавиться от него


                      1. BioHazzardt
                        17.01.2025 12:52

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


        1. mayorovp
          17.01.2025 12:52

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


      1. mvv-rus
        17.01.2025 12:52

        что же ещё он мог подразумевать?

        Да любой объект, принципиально существующий в программе в одном экземпляре, ссылки на который выглядят как независимые объекты. Например - сервисы в контейнере сервисов, получаемые из этого контейнера как ссылки на единственный реализующий их экземпляр. И вместо того, чтобы долго говорить/писать "сервис с временем жизни, совпадающим с временем жизни контейнера" часто для обзначения таких сервисов используют просто одно слово "singleton". И, судя по комментариям, многие именно так автора и поняли. Тем более, что начало описания недостатков - например, в п.1 - полностью применимо и к такому синглетону.
        То, что "синглетон" понимается чисто в смысле "порждающий шаблон Одиночка" из древней книги "четверки", при беглом просмотре неочевидно - ибо книга в тексте статьи и по имени не называется, и приводится не как источник определения, а как пример - короче, при беглом чтении эта отсылка проскакивает незамеченной.
        Я вот тоже понял автора статьи именно так. И даже накатал полстранички комментария с несогласием (к счастью, вовремя понял, что речь у автора не о том). Потому что я, как и многие здесь - не преподаватель, и перечитывать и растолковывать тексты древних (30 лет уже!) книг в мою повседневную работу не входит. А на практике я использование этого шаблона уж и не помню, когда видел. Потому что в том же современном .NET и С# есть куда более удобные для практического применения способы создания таких, единственных в рамках прогрограммы, объектов, чем были 30 лет назад.


        1. Tishka17
          17.01.2025 12:52

          Это хорошо, что вы не видели этот паттерн. Я видел и судя по другим комментариям, люди продолжают использовать. IoC-контейнеры же есть не у всех и не у всех скоуп синглтон называется именно так (это классическое название, но все же). Так что когда говорят "синглтон", без уточнений ожидалсь бы, что речь именно о самостоятельной сущности, а не детали реализации какой-то библиотеки (пусть даже такой большой как спринг и аналоги). Второе ожидание, что люди знакомые с IoC-контейнеоами знают и паттерн и способных их отличить. Вот вы справились, а кто-то - нет, и мне кажется виноват не автор

          Что же касается книги - возраст не повод её выкидывать. Книга все ещё актуальная и рекомендуется к прочтению всем кто переходит от уровень junior дальше.


          1. mvv-rus
            17.01.2025 12:52

            Это хорошо, что вы не видели этот паттерн.

            Увы, это не так. И "давно" не означает "никогда". И в JS можно хоть сейчас написать конструктор для класса, который вернет ссылку не на вновь созданный объект, а на нечто другое - на тот самый синглетон, к примеру (другое дело, что я не фронтовик, и JS мне нужен разве чтобы только сделать макет, который взаимодействует с моим тылом, чтобы проверить, что оттуда приходит).

            Что же касается книги - возраст не повод её выкидывать.
            Ну да. Хотя бы потому, что тамошние "паттерны" - это вполне себе годная основа для коммуникации: благодаря ей многие знают одни и те же приемы под одними и теми же назавниями. А так, не будь этой книги, сами приемы вполне учатся чисто на практике (но без общепринятых названий).


  1. dyadyaSerezha
    17.01.2025 12:52

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

    А некоторые доводы, например как "доказано, что синглтоны ведут к ошибкам", просто откровенно врут - по ссылке нет ни слова про увеличение кол-ва ошибок.


  1. lavr2004
    17.01.2025 12:52

    Не стоит критиковать автора статьи. Просто скажу - не читайте это, - цените своё время. И мой комментарий не читайте ниже... Бессмыслица.

    Человеку нужно было выразиться в тексте и он выбрал самую беззащитную тему - Синглтон. Я думаю такие статьи выйдут и в 2030-ом, и в 42-м годах. Как выходили в 2004-м и 14-м. И жук и жаба пишут эту чушь. Это вид спорта просто такой - писать про GoF.

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


  1. DimaArchUserocher
    17.01.2025 12:52

    Я уже программирую на c# бэкэнд полтора года.Singleton действительно паттерн который в 99% не очень.Но есть исключения.1 - когда иметь больше одного экземпляра не имеет никакого смысла, 2 - когда нам очень важна производительность и мы не хотим несколько раз инициализировать поле.Пример - я сейчас делаю свой мессенджер.Сейчас я пишу бэкэнд.Во время разработки бэкэнда я использую различного рода системы защиты, одно из них хэширование с помощью sha512.Инициализирлвание экземпляра класса SHA512 с помощью метод Build является довольно критическим для производительности.Для того чтобы это решить я создал интерфейс IHashNetwork и обязываю всех наследников реализовывать метод Hash.Потом я создал статический класс HashUtil и создал поле IHashNetwork hashNetwork, потом инициализирую с помощью метода Build.Создал метод HashSHA512 и возвращаю результат метода Hash от hashNetwork.Такое решение является весьма оптимальным, потому что даже если мы например захотим добавить функционал то мы сможем создавать бесконечность классов IHashNetwork и просто их добавлять в общую хэш утилиту.1 - мы повысили производительность, 2 - упростили код.Итог - singleton можно использовать в некоторых ситуациях, но его нельзя назвать паттерном который ты скажешь первым делом на вопрос какие паттерны знаешь.


    1. mayorovp
      17.01.2025 12:52

      А ничего, что SHA512 не является потокобезопасным, а потому никак не может быть синглтоном на бекенде?


  1. Kealon
    17.01.2025 12:52

    Патерн нужный, но как его большинство реализует это ужас.

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