Pull to refresh

Comments 58

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

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

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

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

На самом деле — синглтон прост в понимании, но не совсем прост в использовании. Дело в том, что синглтон предназначен для контроля единственности экземпляра любого класса. Собственно, в этом его основное предназначение и единственная ответственность.

Все копья, которые ломаются вокруг синглтона, связаны в основном с частным (и часто
— ошибочным) случаем его использования, когда он контролирует единственность своего экземпляра и имеет какую-то дополнительную функциональность. Но это проблема не синглтона, а понимания этого шаблона разработчиком и правильности его применения.
Дело в том, что синглтон предназначен для контроля единственности экземпляра любого класса.

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

Если вы готовы сделать сингтон для любого класса (не трогая его самого!) — предоставьте пожалуйста код.

Хм… Да это вроде как должно быть понятно. Упрощенно, что-то типа
class Single {
  static private $instance = null;
  private function __construct() {}
  static public function getInstance() {
    if ( self::$instance === null ) {
      self::$instance = new MyClass ();
    };
    return self::$instance;
  }
}
$myClassInstance = Single::getInstance();

По поводу определения. Загуглил ради интереса — первое — из википедии. Примеров кривых куча, но в определении нет слова своего
Одиночка (англ. Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Как именно ваш код не дает создать другие экземпляры класса MyClass?
Что может помешать мне написать у себя new MyClass и получить второй объект?
Что может помешать мне написать у себя new MyClass и получить второй объект?
Вот! Поэтому разработчик должен понимать, что он делает и для чего он это делает. А не на синглтон все косяки списывать :)
Значит то, что у вас получилось, не является синглтоном по вашему же определению.
Нет, не так.
Это значит, что если у меня в проекте есть необходимость использовать несколько экземляров класса, то я не буду заворачивать его в синглтон. А если я хочу работать только с одним экземпляром (например, это может быть что-нибудь типа PDO), то подумаю над реализацией и синглтон кмк — не самая плохая из возможных.
Дело не в том, что вы хотите, а в том, что ваш пример обертки не синглтон по определению.
Вы никак не можете гарантировать наличие только одного экземпляра любого класса, так как вы не можете заставить весь код (включая тот, который вы не контролируете — библиотечный, например) использовать только вашу обертку вместо конструктора.
Вы как-то все с ног на голову повернули.
Вдумайтесь — я могу заставить весь свой код, который я контролирую, использовать синглтон обертку для класса, который я не контролирую. Разницу видите?
Разницу видите?

Вижу. Эта разница и не дает считать вашу обертку синглтоном.


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

Ваша обертка таких гарантий не дает.
Это не значит, что она плохая.
Просто она не синглтон (по определению от банды четырех).

Это вопрос конкретного ЯП.
Публичный конструктор можно скрыть или запретить.

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

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

https://habrahabr.ru/post/338064/#comment_10462666


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

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

Простите, но это уже глупо. Если вести речь не о своих исходниках, а о закрытых 3rd party, как вы вообще можете гарантировать, что там что-то происходит вот так, а не иначе? Это определенно невозможно — вы не знаете, что есть в черном ящике чужого кода.
Следовательно, это толкование - заведомо неверное.

Я использую общепринятое определение синглтона.

Вы в статье использовали не общепринятое, а свое понимание, сузив шаблон проектирования до класса.

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

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

Я никак не могу, но я и не пытался.


Вы в статье использовали не общепринятое, а свое понимание, сузив шаблон проектирования до класса.

Я не знаю способа реализации синглтона без нарушения SRP и полагаю что его не существует. Вы легко сможете опровергнуть этот тезис просто предъявив код. Пока то что я видел было либо не SRP либо не синглтон.


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

Конечно, нет. Приватные члены вполне нормальны, ненормально отсуствие публичных.

Я не знаю способа реализации синглтона без нарушения SRP и полагаю что его не существует. Вы легко сможете опровергнуть этот тезис просто предъявив код. Пока то что я видел было либо не SRP либо не синглтон.

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

Ну так есть у этого класса публичные методы — работать-то с ним надо. А конструктор — только приватный. И воспользоваться им может только кто-то friendly, второй класс, который вкупе с первым и образует синглтон.
Просто она не синглтон (по определению от банды четырех).
Посмотрите пример на стр. 137. издательство Питер, 2016 и на код создания экземпляров классов BombedMazeFactory и EnhancedMazeFactory и других возможных подклассов
Может это и не синглтон, но только в вашем понимании, а никак не банды четырех.
Вы заимплементили фабрику, а не синглтон.
А кто сказал, что шаблон «синглтон» не может быть реализован посредством фабрики?

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


Если подразумевать другую ответственность (например, только "представить глобальную точку доступа"), то все нормально. Хотя под общепринятое определение перестанет подходить.

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

Как вы предотвратите создания дополнительных экземпляров второго класса, не вмешиваясь в его код?
Ведь у вас же уже есть первый класс, это его ответсвенность.

А кто сказал, что мы не можем вмешиваться в его код, спроектировать его специальным образом, так, чтобы его нельзя было инстанцировать извне просто так, изначально?
В этом случае вы наложите на второй класс ограничения, не связанные с его функциональностью, т.е. возложите на него вторую ответственность.
И первый ваш класс со своей задачей справиться сам не может, т.е. делит свою ответственность со вторым.
Это нарушение SRP с особым цинизмом.
Сокрытие методов не является возложением ответственности на класс. Существование объекта класса — непосредственно часть его функциональности.
Иначе мы можем сказать, что наличие любого конструктора класса — это возложение на него не связанной с его функциональностью ответственности. Вот есть у нас класс String какой-нибудь. Его функциональность — хранение строкового типа данных и разные преобразования этих данных. А вот, мол, создание объекта такого класса с его функциональностью напрямую не связано…

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

Может, но это ограничивается областью видимости.
Сокрытие методов не является возложением ответственности на класс

Отсутствие публичного конструктора или его эквивалента — еще как является.
Сокрытие конструкторов не отвечает за исходную функциональность — оно для этого не нужно.
Это нужно только для реализации синглтона ЧТД.

Отсутствие публичного конструктора или его эквивалента — еще как является.

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

Сокрытие конструкторов не отвечает за исходную функциональность — оно для этого не нужно.

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

Это нужно только для реализации синглтона ЧТД.

… а добавление в класс геттеров нужно только для получения этих данных извне, вот чудо-то.
Это ничего не меняет и не делает сокрытие конструктора побочной ответственностью; это деталь реализации.
Синглтон — класс, реализующий полезную функциональность и контролирующий единственность собственного экземпляра.

Контролировать единственность экземпляра может и компилятор а не только класс.
docs.scala-lang.org/tour/singleton-objects.html
Каждый программный объект имеет одно и только одно назначение.
Его можно исчерпывающе описать одним предложением, не используя союзы.


Тут меня сразу подмывает задать несколько вопросов:
— Назначение объекта типа «калькулятор» складывать или умножать?
— Ограничения на длину предложения существуют?
— Предъявите требования к точности и емкости определения. Например — «Объект реализует функции операционной системы» это ответственность?
— Являются ли ответственностью объекта такие штуки
• Возможность работать на i386, x86_64, ARM
• Умение работать в многопоточной среде
• Переносимость на другие ОС
• Асимптотическая сложность операций
Назначение объекта типа «калькулятор» складывать или умножать?

Назначение объекта "калькулятор" — делать расчеты в соответсвии с исходными данными, используя операции из списка.


Ограничения на длину предложения существуют

Я думаю, достаточно неформального "без фанатизма"


«Объект реализует функции операционной системы» это ответственность

Нет. Нужен список функций или ссылка на него.


Возможность работать на i386, x86_64, ARM
Умение работать в многопоточной среде

Нет. Не указано, что такое "работать" для вашего объекта.


Переносимость на другие ОС
Асимптотическая сложность операций

То же самое. Эти описания неполные.

Назначение объекта «калькулятор» — делать расчеты в соответсвии с исходными данными, используя операции из списка.

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

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

… то это должна быть ссылка. Или параметр конструктора.


Полные описания редко помещаются в одно простое предложение.

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


Любой программный компонент имеет нефункциональные требования

С точки зрения ответственности нефункциональные требования в отрыве от функциональных не имеют смысла.

… то это должна быть ссылка. Или параметр конструктора.

1. Что с ОС делать предлагаете?
2. Если «список операций» это зависимость, то что останется в калькуляторе?
3. Почему то, что останется должно быть именно в калькуляторе, а не передаваться также в качестве зависимости?

Этот принцип неправильно трактуют.


У компонента должна быть одна причина для изменения.
Причину формулирует роль на проекте.
Есть роль DBA и роль Аналитик. Так вот они вместе не должны требовать изменения одного компонента.


Посмотрите работы Роберта Мартина. Есть видео в clean coders, и книга Clean Architecture.

Я читал Роберта Мартина.
Его исходное определение SRP эквивалентно использованному в статье, но на практике очень неудобно для объяснений.

Цитата из последней книги Роберта Мартина: Clean Architecture.


A function should do one, and only one, thing. We use that principle when we are refactoring large functions into smaller functions; we use it at the lowest levels. But it is not one of the SOLID principles—it is not the SRP.
Historically, the SRP has been described this way: A module should have one, and only one, reason to change.
Software systems are changed to satisfy users and stakeholders; those users and stakeholders are the “reason to change” that the principle is talking about. Indeed, we can rephrase the principle to say this: A module should be responsible to one, and only one, user or stakeholder.
Перефразированное определение намного уже исходного.

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

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

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

Я придерживаюсь гораздо более жесткой позиции: пренебрегать критерием единственной ответственности можно только в одноразовом коде на выброс.


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

Это очень дорогой способ. Лучше поработать под началом лида, владеющего SRP.

mkuzmin У компонента должна быть одна причина для изменения. — Что влечет за собой атомарность функционала — два юнита в одной обертке дают две возможных причины для правки. Не знаю — спорю я с Вами или просто уточняю, не важно )

Автор — синглтон становится антипаттерном не от структуры определения или порядка слов в нем а от сферы(практики) применения. Статичные свойства класса внезапно тоже синглтоны — и да, с ними могут быть связаны неприятные моменты, но это не делает их безоговорочно вне закона. В подобной дискуссии я уже как то приводил пример — перечисления в Java реализованы через синглтоны. Объекты window и document в js — синглтоны. Можно натянуть сову на глобус, поменять стандарт и заставить всех получать их через фабрику, сервайс локатор или иной кошерный паттерн но в любом раскладе будет возвращаться один и тот же объект — нафига сову страдать заставили?

>Локатор сервисов — позволяет получить доступ к любому сервису приложения. Это описание без исчерпывающего списка сервисов заведомо неполное.

Массив — структура которая позволяет получить доступ к любому элементу по его индексу.

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

Синглтон требует не только единой точки доступа к экземпляру, так что одного статического свойства недостаточно.
Статичные свойства принадлежат классу а не экземплярам, и для всех экземпляров одного класса они созданы в единственном числе. Что есть в синглтоне и чего нет у статичного свойства что позволяет Вам утверждать что статичное свойстве — не синглтон?
> Объекты window и document в js — синглтоны

Да, мало кто считает яваксрипт образцом соблюдения принципа единственной ответственности.

> Массив — структура которая позволяет получить доступ к любому элементу по его индексу.

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

Давайте проверим.


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

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


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

Делить надо на три класса — еще в одном будет пользовательский сценарий полного циксла печати отчета.


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

Я не знаю такого критерия качества как "здоровье" класса.


Что касается класса, приведённого выше, если произошло изменение в процессе составления отчёта — есть большая вероятность, что в негодность придёт код, отвечающий за печать

Это зависит от характера изменений, а не вероятности. Разумеется, в случае SPR контракт у печатающего класса будет свой и для него определить необходимость изменений станет проще. Но в вики об этом ни слова.


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

Это просто шедевр полезности. Если SRP не нарушен, то его можно не применять.


Слепое следование принципу единственной ответственности приводит к избыточной сложности приложения, его поддержки и тестированию

Это дезинформация в чистом виде, причем без малейших намеков на аргументацию.


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

Эти рекомендации несут прямой и явный вред.


Объединение ответственностей является общепринятой практикой и в этом нет ничего плохого до тех пор пока это легко обслуживать

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


Следование принципу единой ответственности зависит от функций программного продукта и является труднейшим при проектировании приложений.

Неправда. Следовать принципу единственной ответственности просто (это не KISS с YAGNI), сложно выделять ответственности при проектировании.


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

Только вот ORM позволяет выделить из такого объекта ответственность за генерацию и пакетное выполнение запросов к базе.


Итог: статья в вике — очень плохой источник сведений об SRP

Спасибо Вам за подробный анализ. Думаю это хорошее дополнение к статье.
Я не являюсь свитерато-бородатым програмистом в третьем колене, а просто интересуюсь базовыми принципами информатики, так, больше для общего развития. Поэтому для меня наглядность важнее формальной точности. И Википедия в этом плане к сожалению часто тоже не достаточно популярно излагает. С минимальной дидактической редукцией, так сказать.
А сам принцип единственного предназначения (мне такое название больше нравится) довольно интуитивен для понимания: если выключатель в корридоре включает свет и в корридоре и в зале, нам это покажется, как минимум, странным. А создатель такого решения просто подумал, мол почему бы и нет, ведь он, когда приходит домой, сразу идет в зал к телевизору. Звучит вроде обоснованно, но на практике выглядит дико. А в зале есть выключатель который выключает свет в корридоре. (реальный случай из жизни, между прочим)
Или более распространенный случай, но в обратную сторону, у нас два выключателя в корридоре, и оба включают и выключают свет. Тут можно сказать, что нарушается принцип неповторения (DRY), но это так же и случай нарушения единственного предназначения. На один функционал (свет в корридоре) должна быть одна _и только одна_ управляющая компонента (выключатель).
Другой пример — бюрократия, каждое ведомство занимается только своим делом и ничем другим, и передает дело при необходимости другому ведомству. И не может быть двух ведомств имеющих одно и то же предназначение.
Я надеялся в статье на такое вот наглядное описание, для чайников, без избыточного формализма.
если выключатель в корридоре включает свет и в корридоре и в зале, нам это покажется, как минимум, странным.

Почему? Очень удобная реализация варианта использования "не хочу щелкать по всем выключателям".


А в зале есть выключатель который выключает свет в корридоре.

И это удобно.


На один функционал (свет в корридоре) должна быть одна и только одна управляющая компонента (выключатель).

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


И не может быть двух ведомств имеющих одно и то же предназначение.

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


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

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

Эм, создаю синглтоны чере DI контейнер типо сиплинжектора и аутофака и ответственность за существование единственного объекта лежит на них а сам объект вообще может быть не в курсе что он синглтон.
Все верно, только это паттерн DI (внедрение зависимостей), а не синглтон.
Так в DI задается стратегия времени жизни объекта, а клиент об этом ничего не знает.
В DI клиент не знает того, чего и не хочет знать (жизненный цикл своих зависимостей).
Это и позволяет задать любую удобную стратегию в точке сборки.
Sign up to leave a comment.

Articles