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

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

Ради чего всё это?
НЛО прилетело и опубликовало эту надпись здесь
что помимо своих основных обязанностей класс занимается еще и контролированием количества своих экземпляров, чем нарушает Single Responsibility Principle.

The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class

Написано «отдельная часть». Где написано, клас должен поддерживать одну функцию?
Где тут нарушение, если класс заведует тем, для чего предназначен?
over a single part of the functionality
Это аргумент в пользу того, что сказал Lofer.
Singleton — подразумевает, что помимо своих основных обязанностей класс занимается еще и контролированием количества своих экземпляров, чем нарушает Single Responsibility Principle.

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

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


  1. Страшно далека от любой другой функциональности класса.
  2. Находится на заведомо неверном уровне абстракции: количество экземпляров — вопрос использования класса другим кодом, а не его проектирования.
  3. Требует каждый раз думать о зависимостях и многопоточности, плюс копипастить вместо использования готовых инструментов.
  4. Добавляет работы не только при реализации, но и при внесении изменений, требующих все-таки уйти от единственности экземпляра на процесс.
Страшно далека от любой другой функциональности класса.

А какая еще функциональность у этого класса?

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

Т.е вы предлагаете сделать специальный класс, который будет обеспечивать требуемое количество экземпляров в количестве одной штуки? Допустим.
Осталось решить проблему — Запретить создавать более чем одного экземпляра.

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

Если бы многопоточность была самой большой проблемой в этом :). Это Вы еще многопроцессность забыли.

Т.е формально, Singleton разбирвается на следующий перечень задач:

  • Создание экземпляра класса как такового (new… )
  • Запрет/контроль создания экземпляров класса
  • Инициализация экземпляра класса (constructor )
  • Предоставление экземпляра класса (GetInstance() )
  • Безопастность уничтожения экземпляра класса (delete… / IDispose / Finally / ~ destructor / & etc)
  • Безопастность создания экземпляра класса для многпоточного и многопроцессного применения
  • Безопастность инициализации экземпляра класса для многопоточного и многопроцессного применения
  • Безопастность уничтожения экземпляра класса для многопоточного и многопроцессного применения


Судя по вашей логике — понятие Single у Вас это атомарные функции? Может стоит «без фанатизма»?
А какая еще функциональность у этого класса?

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


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

Я больше скажу — такие классы давно уже есть. Не в курсе конкретно про iOS но свои аналоги Lazy и DI-контейнеры на львиной доле популярных платформ 99% существуют и активно используются.


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

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


Если бы многопоточность была самой большой проблемой в этом

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

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

Банально: new Singleton() в ненужном месте должен вызывать ошибку уровня компилятора и принудительно от программиста требовать использовать иных методов.
Это, банально, дешевле и быстрее, чем гонять кучу тестов а потом искать «плавающие» баги.

Я больше скажу — такие классы давно уже есть. Не в курсе конкретно про iOS но свои аналоги Lazy и DI-контейнеры на львиной доле популярных платформ 99% существуют и активно используются.

От спасибо… порадовали старика, а то маразм забывать стал :)

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

ну ну…
Банально: new Singleton() в ненужном месте должен вызывать ошибку уровня компилятора

Еще более банально: не хотите давать сконструировать экземпляр класса в каком-то конкретном месте — используйте интерфейсы или абстрактные классы. Синглтон тут помогает примерно как гильотина от головной боли.


Контейнер может как-то защитить от банального вызова «new» не там где нужно?

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


Т.е вы полагаете что DI и контейнеры как-то «магически» создают классы заведомо правильно? программист не может накосячить в жизненном цикле? или сам контейнер не может накосячить?

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

Еще более банально: не хотите давать сконструировать экземпляр класса в каком-то конкретном месте — используйте интерфейсы или абстрактные классы.

Т.е у вас везде и всегда будут интерфейсы. Допустим.
Из этого возникает вопрос: кто и когда будет вызвать new()?

Кто: точка сборки, она же Composition Root
Когда: когда необходимо приложению.

Т.е. В приложении у вас получается: куча интерфейсов, наличие публичного конструктора классов, иначе DI механизм просто не заработает, обязательное использование DI.
Возвращаемся к вопросам выше:
  • как применение DI гарантирует, что нигде кроме DI не будет вызван new () для класса, который должен быть в единственном экземпляре в рамках одного контекста (одного потока, нескольких потоков, процесса) ?
  • Какими конкретно техническими приемами буду обеспечены гарантии, не зависимо от архитектуры решения (язка программирования, платформы, применяемых инструментариев) ?
В приложении у вас получается: куча интерфейсов, наличие публичного конструктора классов, иначе DI механизм просто не заработает, обязательное использование DI.

Как будто что-то плохое.


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

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

По построению: никто, кроме точки сборки, не имеет даже ссылок на библиотеку со столь оберегаемым классом.

Т.е никаких технических решение вы не предлагаете, как гарантировать уникальность экземляра класса в рамках какого-то контекста исполнения. Для этого вам нужны какие-то дополнительные администативные мероприятия, выходящие за рамки чисто технического решения.
Вас не смущает, что вы не можете решить техническую задачу, техническими средствами?
Возможно Ваше решение не подходит к задаче? Или вы просто пытаетесь «ремонтировать» то, что работает, просто не поняв назначение?

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

Вы действительно прочли мой ответ? Там изложено именно техническое решение.


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

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

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

Значит не сталкивались.

(устраняется после первого же ревью навсегда, если вообще возникает),

«Ревью» разве не административное решение?

Я не очень понимаю, как дальше рассматривать «не существующую проблему» и ее решения.
НЛО прилетело и опубликовало эту надпись здесь
Значит не сталкивались.

Вот с чем-чем, но с этим не сталкивался. А синглтоны в легаси иногда мешали сильно.


«Ревью» разве не административное решение?

А целый абзац выше вы прочли?
Там исключительно технические средства, дающие заведомо лучший результат, чем синглтон.
Другое дело, что описанная вами проблема в моей личной практике таковой не является. Прямой вызов конструктора где не надо — это маргинальный частный случай антипаттерна Control Freak, который синглтонами только усугубляется. Если делаете библиотеку, то API на интерфейсах однозначно проще и универсальнее. Если контролируете клиентский код, то ревью все равно делать надо.
Синглтон здесь выглядит как попытка получить хоть шерсти клок с паршивой овцы-антипаттерна. Но даже с этим он справляется плохо.

А синглтоны в легаси иногда мешали сильно.

Это специфические решение. Для узкого круга задач.

Если делаете библиотеку, то API на интерфейсах однозначно проще и универсальнее.

Это не отрицается. Вопрос не в наличи интерфесов или абстрактных классах, а в количестве экземплятров класса в каком-то контексте исполнения.

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

Решение в том, что клиентский код даже не знает что писать после new. У него физически нет доступа к имплементации интерфейса.

Фактически, изоляция new() переносится с уровня класса и одного метода, на уровень библиотеки. Вместо одной точки «накосячить» в классе ( и то минимально), мы размазываем возможность «накосячить» по всей библиоткеке или приложению.

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

Это тоже решение. Оно имеет право на жизнь.
НЛО прилетело и опубликовало эту надпись здесь
Но весь смысл DI в том, чтобы избавить клиентский код от отвественности за инстанциирование объектов.

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

DI.Init(ITargetTypeInterface, ClassType, LifeCyrcleScope.Singleton);
и
DI.Init(ITargetTypeInterface, new ClassType(), LifeCyrcleScope.Singleton);

а после просто дергать
1. ITargetTypeInterface temp = DI.Resolve(ITargetTypeInterface)
или
2. ITargetTypeInterface temp = new ClassType()
...
temp.Fn(bla bla bla);


Вот п2 вы не можете проконролировать, без «Решение в том, что клиентский код даже не знает что писать после new. У него физически нет доступа к имплементации интерфейса.» и/или «Review»
НЛО прилетело и опубликовало эту надпись здесь
Ну вот клиентскому коду не нужно

Есть разница между «не нужно знать» и «не иметь возможности»
Весь мир состоит только в клиентском коде или есть другие слои? :)
НЛО прилетело и опубликовало эту надпись здесь
Конкретно за new отвечает контейнер и код, который его конфигурирует

Рассматривайте Singleton как ответственный за запрет кому либо создавать свои экземпляры.
НЛО прилетело и опубликовало эту надпись здесь
И все ради чего? Ради чего писать код синглтона?

Задачи противоположные по смыслу:
DI — обязанность создавать экземпляры.
Singleton — запрет создания экземпляров.

Ради чего вносить неявные зависимости? Ради того, чтобы не использовать DI?

Если будет написано:
import/include PuperDI;
import/include MyCode;

DI di = new PuperDI.DI();
di.Init (..., MyCode.MySingleton, ...)

... = PuperDI.DI.Resolve(MyCode.MySingleton).Fn();

будет меньше зависимостей чем от:
import/include MyCode;

... = MyCode.MySingleton.GetInstance().Fn();

?

Вы серьезно полагаете, что на DI свет клином сошелся и если его не использовать, то небо на замлю упадет?
НЛО прилетело и опубликовало эту надпись здесь
Задачи противоположные по смыслу:
DI — обязанность создавать экземпляры.
Singleton — запрет создания экземпляров.

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


… = PuperDI.DI.Resolve(MyCode.MySingleton).Fn();

Это вообще не DI, а известный антипаттерн Service Locator.
Обычный класс, спроектированный в соответствии с DI, никакими контейнерами не пользуется, а получает свои зависимости в параметрах конструктора.
Контейнер может (не обязан!) использоваться в точке сборки для упрощения ее реализации.

Создание такой ответственности — прямой вред для любого использования класса.
Принцип "в компании может быть только один директор" реализуется на уровне класса Компания, а не класса Директор.
Второй вариант — грубая ошибка дизайна.

Это специфические решение. Для узкого круга задач.

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


Вопрос не в наличи интерфесов или абстрактных классах, а в количестве экземплятров класса в каком-то контексте исполнения.

И этот вопрос должен быть решен на уровне контекста исполнения, а не класса.


Вместо одной точки «накосячить» в классе ( и то минимально), мы размазываем возможность «накосячить» по всей библиоткеке или приложению.

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


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

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

Мы подразумеваем одно и то-же под DI?
Dependency injection
In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object.
Dependency_injection

Forms of Dependency Injection
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2
Figure 2: The dependencies for a Dependency Injector
injection


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

В общем случае этот принцип называется
InversionOfControl
Inversion of Control is a common phenomenon that you come across when extending frameworks. Indeed it's often seen as a defining characteristic of a framework.
А DI — это механизм в рамках реализаци IoC. Наверное, стоит их разделять :)

Вроде нет такого «религиозного канона» :) Куда хочешь, туда и передавай свои зависисмости: хочешь через конструктор (Constructor Injection), хочешь через свойства или методы(Setter Injection/ Interface Injection).
Другое дело, что через конструктор контролировать чуть проще, так контейнеры и должны обеспечивают корректную передачу/инъекцию.

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

Никто не спрашивал :)
Работа с эксклюзивными ресурсами или критическими к порядку операций и глобальным состоянием внешнего ресурса/сервиса.
Вы будете привязывать свою служебную низкоуровневую библиотеку к какому-то удобному и популярному сейчас контейнеру? А потом будете в бизнес-приложении пытаться разрулить это «фарш»? Или вы свое бизнес приложение будете подстравивать под весь зоопарк контейнеров которые используют служебные библиотеки?
Сложно придумать способ побольнее «выстрелить себе в ногу».
Это вообще не DI, а известный антипаттерн Service Locator.

Посмотрите как нибудь исходники контейнеров за фасад «магии».

И этот вопрос должен быть решен на уровне контекста исполнения, а не класса.

Технически такие экземпляры классов и находятся на уровне контекста исполнения :) без промежуточных «подпорок».
Мы подразумеваем одно и то-же под DI?

Вы сами привели (и подчеркнули) цитаты, подтверждающую мои слова.


one object supplies the dependencies of another object.
is to have a separate object, an assembler, that populates a field in the lister class

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


Вроде нет такого «религиозного канона»

Ничего не знаю про религиозные каноны, но Service Locator — это НЕ Dependency Injection по построению. Injection прямо указывает, что зависимости разрешает и внедряет кто-то снаружи — никаких вызовов Resolve вне точки сборки нет и быть не может, не должно быть даже ссылок на сборку с контейнером.
Посмотрите у Симана, очень подробно разжевано.
Пока вы будете под видом DI делать Service Locator, вроде такого...


PuperDI.DI.Resolve(MyCode.MySingleton).Fn();

… ничего хорошего у вас не выйдет.


Никто не спрашивал

Я спрашивал пример вашего кода с реализацией синглтона без копипасты. До сих пор не дождался.


Посмотрите как нибудь исходники контейнеров за фасад «магии».

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


А потом будете в бизнес-приложении пытаться разрулить это «фарш»?.

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


Технически такие экземпляры классов и находятся на уровне контекста исполнения

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


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

А конкретно? Какой ресурс в вашей личной практике потребовал синглтон как лучшую альтернативу?

А все остальные этим НЕ занимаются, в том числе НЕ вызывают конструкторы своих зависимостей и НЕ вызывают никаких методов Resolve.

Я такое никогда не утверждал, возможно, вы не корректно поняли.
Я так-же упоминал, что дергают конструкторы или контейнеры внутри себя при разрешении зависимостей или могут использовать экземпляры классов созданные вне контейнеров в точке конфигурации.
Поэтому и упомянул «Посмотрите как нибудь исходники контейнеров за фасад «магии».»
По какой-то причине, вы настойчиво указываете «наверх» приложения, где-то в районе бизнес-логики и выше. Я же предлагал попробовать посмотреть «под капот»: внутрь IoC контейнеров которые реализуют DI, внутрь тех библиотек, которые предоставляют классы для IoC контейнеров, внутрь тех библиотек, которые просто запускают приложение внутри ОС и т.д.
Там немного другие приоритеты к требованиям.
Те же требования к производительности, к памяти, к 100% предсказуемости поведения приложения когда и что будет создано и уничтожено. Или по вашему «абстрации» абсолютно бесплатная штука?
А предлагал посмотреть на фундамент здания, а вы мне упорно показываете планировку квартиры в этом здании.
Свая здания и ножка стола в квартие делают одно и тоже, но приоритет требований к ним разный.
Я такое никогда не утверждал, возможно, вы не корректно поняли.

Неужели?


Задачи противоположные по смыслу:
DI — обязанность создавать экземпляры.
Singleton — запрет создания экземпляров

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


По какой-то причине, вы настойчиво указываете «наверх» приложения, где-то в районе бизнес-логики и выше

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

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

Интересно, что делать, если в точке сборке, в момент запуска приложения я заранее не знаю, сколько мне потребуется экземпляров?

Например, у меня приложение — многооконный текстовый редактор, у каждого документа есть ViewModel со ссылками на классы-сервисы. И ViewModel создаётся, когда юзер выполняет File→New или File→Open.

Для создания нового экземпляра ViewModel можно обойтись без new или resolve?

Хотя… тут, похоже, ключевое слово — своих зависимостей
тут, похоже, ключевое слово — своих зависимостей
Но, с другой стороны, AppMainWindow зависит же от DocumentViewModel. Значит, не должно их создавать…

Либо тут надо городить фабрику на ровном месте, а ViewModel не использовать напрямую, только через интерфейсы. Как-то громоздко.
Либо тут надо городить фабрику на ровном месте, а ViewModel использовать только через интерфейсы
Хотя, почему бы и нет. Рай для unit-тестирования. Можно приложению подставлять любые фабрики моделей, которые создадут любые mock-модели.

Фабрика на ровном месте в современных контейнерах имеет нулевой оверхед по части кодирования — вызов фабричного делегата трансформируется в вызов конструктора автоматически.

Не очень понятно, это замечание по производительности приложения?

Кодировать-то надо намного больше. Вместо прежнего
DocViewModel vm = container.Resove<DocViewModel>();
workplace.ShowModal(vm);
будет похожее
IDocViewModel vm = factory.Create();
workplace.ShowModal(vm);

Но появляются «лишние» 3 файла:

1) описание IDocViewModel (которое придётся синхронизировать с DocViewModel при изменениях),
2) описание IDocViewModelFactory,
3) реализация DocViewModelFactory.

Как избавиться от всего этого boilerplate, непонятно.

Выделение интерфейса — это не фабрика, это разделение ответственностей на контракт и реализацию.
А собственно с фабрикой все просто — делаем параметр конструктора с типом Func<int, string, IDocViewModel> и получаем фабрику с двумя параметрами. При вызове контейнер подставит переданные параметры в параметры конструктора того же типа, а остальные разрешит в соответствии с регистрациями. В результате имеем один анонимный делегат вместо интерфейса и класса, остальное контейнер делает за нас.

Не успеваю следить за руками :(
int и string — откуда появились?

Пардон, пропустил пару промежуточных звеньев.
Основная мысль — замена интерфейса и реализации фабрики параметром-делегатом.
Если нам на уровне клиента параметризовать создание DocViewModel не надо, то достаточно сделать параметр конструктора с типом Funk< IDocViewModel>
Современные контейнеры достаточно умны, чтобы не найдя регистрации самого типа-делегата, начать искать регистрацию IDocViewModel, найти что этому интерфейсу сопоставлен класс DocViewModel, найти его конструктор, разрешить его параметры, сделать вызов и вернуть результат.
То бишь всю "фабричную" работу контейнер берет на себя.
Я описал более сложный вариант, когда фабричный метод имеет параметры — контейнер и в этом случае может отработать автоматически, связав параметры делегата и параметры конструктора класса DocViewModel.

Интересный приём, надо взять на заметку.
Либо тут надо городить фабрику на ровном месте,

Просто метод Resolve используется вместо фабрики и возвращает готовый собранный объект, котороый просто может быть использован в точке File->New/Open.
В этом прелесть контейнеров IoC :) И следствие, как Вы, правильно заметили: Рай для unit-тестирования.
метод Resolve используется вместо фабрики
Тогда появляется зависимость от контейнера, чего последняя мода на архитектуру крайне не рекомендует.
Увы. С чудесами сложно в создании объектов:
  • создать самому — что «не правильно»
  • сделать фабрику, которая будет создавать объекты, которую тоже как-то надо создать и сделать доступной.
  • фабрика, будет по факту делать то-же что и контейнер, почему-бы не заинъектить контейнер.


Осталость выбрать между «не правильными» вариантами :)
Зачем выбирать неправильный, когда есть правильный.

1. Делаем фабрику с единственной отвественностью return new DocumentViewModel

2. Прячем абстракцию фабрики за интерфейс, приложение работает с фабрикой не напрямую, а через абстракцию.

3. Связываю абстракцию с конкретной фабрикой в точке сборки.

Вроде, все овцы сыты?
Берем фабрику и «обучаем» ее создавать нужные экземпляры классов.
Берем контейнер, конфигуриуем его создавать нужные экземпляры классов.
Хотим возвращает интерфейсы, хотим — классы. Не суть важно.
Оба механизма делают абсолютно тоже самое с абсолютно одинаковым результатом.

Только что бы согласно «концепции» иньектить интерфейс фабрики, вместо интерфейса контейнера? Отличие в концептуальной «чистоте ?» Если не видно разницы, зачем платить больше?
НЛО прилетело и опубликовало эту надпись здесь
Банально по конструктору не видно что конкретно нужно.

то флаг и глобалы вам в руки

Делается что-то вроде:
class MyDummyLogic
{
//@Inject
//[Inject]
public IIoc MyIoC{get;set;}

public void MyNewEntyty()
{
MyEntity entity = this.MyIoC.Resolve(MyEntity);
}
}

или

class MyDummyLogic
{
//@Inject
//[Inject]
public IMyFabric MyFabric{get;set;}

public void MyNewEntyty()
{
MyEntity entity = this.MyFabric.CreateEntity();
}
}


Откуда берутся глобалы-то? Зачем?
НЛО прилетело и опубликовало эту надпись здесь
Вопросов нет. В целом согласен.
Берем фабрику и «обучаем» ее создавать нужные экземпляры классов.
Берем контейнер, конфигуриуем его создавать нужные экземпляры классов.
Хотим возвращает интерфейсы, хотим — классы. Не суть важно.
Оба механизма делают абсолютно тоже самое с абсолютно одинаковым результатом.

Фабрика:


  1. Возвращает реализацию конкретного интерфейса с запрошенными параметрами.
  2. Ошибки во время выполнения могут быть связаны только с неверными параметрами или исчерпанием ресурсов
  3. Для реализации с любой целью достаточно знать ее контракт.

Контейнер:


  1. Возвращает реализацию любого интерфейса, самостоятельно определяя подходящую фабрику среди зарегистрированных
  2. Ошибки во время выполнения дополнительно могут быть связаны с конфигурацией контейнера.
  3. Для реализации необходимо помимо контракта надо точно знать еще и требования к конфигурации.

Такое вот "абсолютно тоже самое с абсолютно одинаковым результатом".
Именно поэтому инъектить контейнер — грубая ошибка и антипаттерн.
Добавляя контейнер вместо реальных зависимостей, получаем огромные проблемы с сопровождением:


  1. Теперь для понимания реальных зависимостей класса вместо имени, интерфейсов и параметров конструктора (единицы строк, обычно две) надо читать весь его код (от десятков до десятков тысяч строк). Уже за одно это нарушение инкапсуляции попытка использовать контейнер как Service Locator должна быть зарублена и в code guide, и на ревью, а то и еще при сборке.
  2. Теперь обычные классы зависят от конкретного контейнера и не могут быть переиспользованы без него.
  3. Ошибки в конфигурации контейнера могут не проявляться при конструировании класса, а будут ждать конкретного вызова Resolve и это нельзя исправить за счет лучшей реализации контейнера.
  4. Код в юнит тестах требует использования контейнеров и неочевидной их конфигурации, что самым пагубным образом сказывается на его качестве.

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

Хороший рай для тестирования: чем фаршировать контейнер по сигнатуре конструктора понять нельзя — надо смотреть код класса целиком.

Такое вот «абсолютно тоже самое с абсолютно одинаковым результатом».

Рассматривалось в контекте одноразововой ситуации «File->New».

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

Дайте я угадаю. Эту фабрику нужно будет предварительно создать через new или придется где-то дергать метод типа Resolve? Откуда ее экземпляр появится ?

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

Что вы прицепились к бедному конструктору? Я уже показал что инъектить можно через «любую дырку» (хоть в private members, хотя и не одобряю такую практику). Религиозных «Шаблонных» запретов на это нет.
Во вторых — с какого перепугу лопатить «тысячи строк»? Если у вас такой CodeStyle — можно попробовать привести его в порядок.

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

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

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

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

Определитесь с понятием «Single responsibility», на какой контекст распростроняется? На атомарную функция? кусок функционала покрыващий некоторый набор требования?
Если вы вас так раздражает Singleton, давайте «померяем» вашими мерками IoС. Он окажется еще более «ужасен» — мало того, что обладает функционалом создавать экземпляры классов, «ковыряться» в чужих классах что бы инъектить, ковыряться" в чужих классах что бы дергать фабрики, должен приводит типы, так эта «зараза» еще и… следит за количеством экземпялов класса ?!
Одну ужасную штуку, мы заменили еще более ужасной! О ужас!

Пока я вижу один аргумент:
Трактовка «In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. » — это просто не правильное желание разработчиков. Они были идиотами, сформулировав такую задачу и еще большими идиотами, что использовали такой вариант решения.
Да, нюанс «restricts» — создает массу проблем, но увы, его надо реализовать. Все решения — административное в виде «review» или «спрятать имплементация, показать интерефейс», не выполнимы на каком-то из уровней имплементации и решают поставленную задачу частично, переносится «Singleton» и «restricts» из самого класса в какое-то иное место в каком-то классе, наделяя его или не свойтственными функциями, тем самым нарушая SRP в другом классе.

Не важно, решает ли pattern задачу, и тем более не важно какой ценой. Главное не отступить от Религиозных Шаблонов Проектирования.

Только, похоже вы забыли одну маленькую деталь. Design Patterns — это не набор Канонов и Истина. Это просто… инженерный подход, для повторного использования решений уже известных проблем, похожий на бинарные библиотеки или исходный код. Только тут повторное использование на уровне Проблема-Проектное решение. Поэтому они и называются Design Patterns — шаблоны проектирования.
Software design pattern
In software engineering, a software design pattern is a general reusable solution to a commonly occurring problem within a given context in software design.
Вас ранее просили дать конкретный пример, когда Singleton хорошо подходит.

У меня есть анти-пример.

Многооконное приложение, имеющее глобальные настройки.
Доступ к настройкам осуществлялся через Singleton:
Config::getInstance()->getBgColor();
Config::getInstance()->getDefaultMargins();

Всё это потребовало коренной перестройки, когда приложение стало открывать разные типы документов, у которых были разные «глобальные» настройки. Пришлось этот Singleton выпиливать.
Вас ранее просили дать конкретный пример, когда Singleton хорошо подходит.

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

У меня есть анти-пример.

Пичалька. Небольшой косяк имплементации, не предусмотрели «Feature Extensions» Если бы не было лень, было бы что-то типа:

// не будем придираться к shared_ptr/_com_ptr_t :)
IConfig *pConfig = Config::getInstance();

pConfig->getBgColor();
pConfig->getDefaultMargins();

после вопрос «выпилить» не стоял бы так остро.
Но вопрос то не об этом.

Разберите Singleton на части и что там обнаружится?
  1. Защищенное хранилище экземпляра класса, к каком-то контексте.
  2. Механизм доступа к защищенному экземпляру класса
  3. Логика, поверяющая наличие экземпляра класса в хранилище.
  4. Создание экземпляра класса в контексте п1
  5. Запрет создание экземпляра класса ЗА пределами контекста п1


Если рассмотрим предлагаемые ранее решения, вместо «плохого» Singleton в одном классе, то обнаружим, что:
  • пункт 1, переносится из класса, в какое-то другой клас или вспомогательной библиотеки. (опустим многопоточность и Thread-local storage)
  • пункт 2, переносится из класса, на уровень библиотеки классов
  • пункт 3, переносится из класса, на уровень вспомогательной библиотеки/контейнера
  • пункт 4, переносится из класса, на уровень вспомогательной библиотеки/контейнера
  • пункт 5, переносится из класса, на уровень хз?.. допустим вспомогательной библиотеки/контейнера.


Но вот незадача… Контейнер и библитека класса исполняются в контексте бизнес логики, иначе контейнер на сможет создать экземпляр класса.
Если задача, в том, что бы «prevents», то нужно их как-то изолировать друг от друга, что бы BL никак не могла напрямую создать экземпляр класса.

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

Изоляция компонентов хорошо? Да хорошо.
IoC / DI это хорошо? Да хорошо.
Хреновый дизайн плохо? да плохо.
Попробуйте реализацию журналирования и странзакциоонности для файловой системы, для примера.
Отличный анти-пример :)

В данном случае в OS будет работать ровно один том с этой FS. Когда понадобится эту файловую систему обслуживать на нескольких дисках (где у каждого свой журнал), singleton придётся выпиливать.

Есть другой пример?

Пичалька. Небольшой косяк имплементации, не предусмотрели «Feature Extensions» Если бы не было лень, было бы что-то типа
Увы, не понял идею, которую вы хотели донести.

В чём разница между
color = Config::getInstance()->getBgColor();
и
IConfig *pConfig = Config::getInstance();
color = pConfig->getBgColor();

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

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

То есть, Singleton изначально был не нужен.

Это называется — не предусмотрены точки расширения с точки зрения архитектуры.
Или ваш редактор уже может работать в виртуальной реальности или хотят бы может работать на паре платформ и паре CPU арихитектур? Нет? Код надо причесать под новые требования? Так у вас тоже косячексъ в архитектуре.

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

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

Какие? Пока все что показывалось, сводилось к «спрячем» инстанс и спрячем создание от вызывающей стороны. Сделаем менеджер доступа для вызывающей стороны и создания количества экземпляров.

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

Есть такое. Но у БД их сколько?
Файловый объект — в него не могут гадить все сразу, если требуется сохранить логческую целостность, иначе получается фарш.
Сокеты — не могут в него гадить все сразу, иначе логическая целостность будет нарушена с отправляющей стороны. и т.д.
Да, можно сказать, что «есть файловые дескрипторы, семафоры и т.д.». Но что мы видим? Тоже самое — экземпляры не создаются вызывающей стороной, они хранятся в недоступном вызывающей стороной месте, и есть API для управления экземплярами.
Тут я совсем потерялся, чем синглтон в принципе может помочь.
Кто мешает «гадить всем сразу», вызывая в разных потоках
Singleton::getInstance()->doDirtyThing()
Кто мешает «гадить всем сразу», вызывая в разных потоках

Начинаем сначала:
The singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
Определите какие границы вашей «the system» это процесс? Поток? запрос? Вот в этих рамках и ваяйте «restricts» и .«one object».

Так синглтон "в этих рамках" не умеет.
У него рамка сверхжесткая — один процесс, ни больше ни меньше.

Так синглтон «в этих рамках» не умеет.

Вы смотрите конкретныю имплеметацию, на конкретном языке.
Например для .Net согласно ECMA «Common Language Infrastructure (CLI) Partition I Concepts and Architecture» private static field будет виден далеко не по процессу:
Static fields and static methods
Static fields are always restricted to a single application domain basis (see §12.5), but they can also be allocated on a per-thread basis.

А внутри, если посмотреть, будет механизм Thread Local Storage
The following diagram illustrates how TLS works.
Что, как мы понимаем сильно отличается от private static member для С++
C++ позволяет самому выбирать что, где и как хранить на низком уровне. .Net и Java такого не позволяют. Что бы детишки не поранились сильно, им дали пластиковый ножик :)
Дизайну без разницы на конкретную имплементацию, если решает задачу.

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

синглтон… жестко зависит еще и от платформы

Имплементация — да.
Молоток стоматолога отличается от кувалды кузнеца как и от копра или отбойного молотка.
А цель — оказать ударную нагрузку/передать кинетическую энергию выполняет.
Если для вас молоток которым забивают гвозди и молоток для отбивания мяса это два принципиально разных молотка делающие разные вещи (по месту применения, а не по механике действия) тогда да.
Мне не интерсно обсуждать ручку молотков, мне интересно передача кинетичесой энергии в этой теме.
синглтон… по какому объективному критерию с DI не сравнивай

Так их и нельзя сравнивать. У них разные задачи.
Я уже показывал, что приводимые приемы заменить Singleton на DI + IoC по конечному результату дают тот же… SIngleton. Потому что задача «запрет более одного экземпляра класса в границах системы» никуда не делась.

вопрос «какие границы вашей «the system» это процесс? Поток? запрос? класс? и т.д.»
Вопрос как в этих границах у вас резализован «restricts»?
как только вы ответите себе на эти вопросы, все станет на свои места.
Так их и нельзя сравнивать. У них разные задачи.

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


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

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


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

самому выбирать что, где и как хранить на низком уровне. .Net и Java такого не позволяют. Что бы детишки не поранились сильно, им дали пластиковый ножик :)
Это к чему? Вы же сами цитировали — «but they can also be allocated on a per-thread basis», т.е. в c# есть и static, и threadstatic.

Насчёт привязки к Application Domain — это аналогично разделению по процессам в c++. Там тоже нельзя (ну, только средствами языка:) сделать static, расшаренный между процессами.
т.е. в c# есть и static, и threadstatic.

A static field marked with ThreadStaticAttribute is not shared between threads. Each executing thread has a separate instance of the field, and independently sets and gets values for that field. If the field is accessed on a different thread, it will contain a different value.

An application domain is intended to isolate two applications running in the same OS process from one another by guaranteeing that they have no shared data.


Технически это означает что оно шарится по самому безопасному сценарию, а разработчик может сделать еще «безопаснее» (на вилку одеть пробку, что бы детишки себе глазик пластиковой вилочкой случайно не тыкнули)
Насчёт привязки к Application Domain — это аналогично разделению по процессам в c++. Там тоже нельзя (ну, только средствами языка:) сделать static, расшаренный между процессами.

Между процессами (OS Process ) или потоками (OS Threads )? И как вы будете шарить между процессами (OS Process) singleton в таком случае?
Технически это означает что оно шарится по самому безопасному сценарию, а разработчик может сделать еще «безопаснее» (на вилку одеть пробку, что бы детишки себе глазик пластиковой вилочкой случайно не тыкнули)
Я офигеваю с ваших представлений о static и threadstatic.

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

И как вы будете шарить между процессами (OS Process) singleton в таком случае?
singleton никак, потому что он кривой ))

static данные можно шарить средствами OS, замапливая одинаковые страницы памяти в разные процессы.
Я офигеваю с ваших представлений о static и threadstatic.

Мои то тут причем? Это цитаты из документация М$ по имплемнтации .Net и EСMA спецификация. С них и офигевайте :)

И как вы будете шарить между процессами (OS Process) singleton в таком случае?
singleton никак, потому что он кривой ))


static данные можно шарить средствами OS, замапливая одинаковые страницы памяти в разные процессы.

А кто за это отвечает? Кто создает разделямые страницы памяти и «мапит одинаковые страницы памяти в разные процессы»? Это ваш код или приложение работает с каким-то API?
то цитаты из документация М$ по имплемнтации .Net и EСMA спецификация. С них и офигевайте :)
Я офигеваю, как в этом можно увидеть «детишки себе глазик пластиковой вилочкой случайно не тыкнули»
А кто за это отвечает?
OS
Я офигеваю, как в этом можно увидеть «детишки себе глазик пластиковой вилочкой случайно не тыкнули»

M$ всегда декларировала безопастность как одна из целей .Net платформы.

Overview of the .NET Framework

In addition, the managed environment of the runtime eliminates many common software issues. For example, the runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. This automatic memory management resolves the two most common application errors, memory leaks and invalid memory references.

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

Впрочем, .Net сейчас открыт — можете поправить M$, заодно обновить ECMA и ISO спецификации.
А кто за это отвечает?
OS

А конкретнее можете сказать какой компонент OS?
А конкретнее можете сказать какой компонент OS?
Менеджер виртуальной памяти за это отвечает.

Если интересна практическая сторона — ф-цией CreateFileMapping можно создать именованный объект, не привязанный к файлу (hFile = INVALID_HANDLE_VALUE, lpName != null). Этому объекту будет выделена память, которую можно будет одновременно замапить в разные процессы ф-цией MapViewOfFile.
А сколько этих менеджеров виртуальной памяти?
Я уверен, менеджер вирт. памяти не использует паттерн «синглтон». Можно по линуксу проверить, и по другим ОС с открытыми исходниками ;)
Т.е вы предполагаете, что их (менеджер вирт. памяти) много, их может создавать кто угодно и когда угодно в рамках ОС?
На этот аргумент вам уже отвечал другой оппонент.

Приложение не может создать ещё один VMM не потому, что он SIngleton, а потому что нет таких API (другими словами, приложение имеет доступ к интерфейсу, но не к реализации, поэтому при всём желании не может вызвать конструктор).

Внутри ядра ОС — пожалуйста, создавай сколько хочешь (если логика ОС подразумевает, например, отдельные виртуализированные машины).
На этот аргумент вам уже отвечал другой оппонент.

Приложение не может создать ещё один VMM не потому, что он SIngleton, а потому что нет таких API (другими словами, приложение имеет доступ к интерфейсу, но не к реализации, поэтому при всём желании не может вызвать конструктор).

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

Именно! Границы системы, внутри которых требуется один экземпляр и нельзя плодить новые! Детали имплементации — не важны и зависят от более детальных требований.
Именно!
Именно что? Давайте подведём вывод к обсуждению тезиса «Singleton — антипаттерн».

Все вышесказанное доказывает, что для обеспечения нужного поведения Singleton не требуется.
Кто мешает «гадить всем сразу», вызывая в разных потоках
Определите какие границы вашей «the system» это процесс? Поток? запрос? Вот в этих рамках и ваяйте «restricts» и .«one object».
Чем дальше в лес, тем сильнее вы пытаетесь натянуть сову на глобус. Лишь бы не признаваться, что синглтон для файловой системы или сокетов — так себе идея.

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

qw1,
Т.е. сначала, один журнал файловой системы на всё приложение — это благо, а потом оказывается, что их можно создавать в каждом потоке. Что будет с бедной файловой системой, остаётся только догадываться.

qw1, простите, Вы читать умеете? Или у Вас случайно получилось приписать слова которые не говорил ваш оппонент/собеседник? Не могли бы Вы привести цитату, для подтверждения Ваших слов?

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

С интересом ознакомлюсь с примером работы с общим ресурсом, так что бы это имело логический смысл, предсказуемое проверяемое состояние после операций, а не «фарш» на выходе.
Можем для простоты рассмотреть файл в Windows, в рамках одного потока, нескольких потоков, в рамках нескольких процессов.
С интересом ознакомлюсь с примером
Вот пример — логгер. Когда-то я его делал синглтоном… Лет 5 назад. Теперь мне очевидно, что это идея не очень.

Зачем связывать «фарш» многопоточного доступа и требование единственности объекта определённого класса в системе. Это ограничения совершенно разной природы.
Можем для простоты рассмотреть файл в Windows
Как скучно. Тут ОС делает всю работу.
Вот пример — логгер. Когда-то я его делал синглтоном… Лет 5 назад. Теперь мне очевидно, что это идея не очень.

Если я верно понял, у вас несколько экземпляров логеров, которые работают в нескольких потоках, и все они пишут в один файл?

Можем для простоты рассмотреть файл в Windows

Как скучно. Тут ОС делает всю работу.

Магия?
Если я верно понял, у вас несколько экземпляров логеров, которые работают в нескольких потоках, и все они пишут в один файл?
В чём проблема создать один логгер (идеально — в точке сборки) и передать его в классы, которые в нём нуждаются. Плюс этого решения (по сравнению с синглтоном) — если я захочу, чтобы какой-то экземпляр (даже не класс!) писал в другой файл, или не писал вовсе, я ему просто передам другой логгер, не влезая в код класса.
Магия?
Есть такая штука, как static. И для неё singleton не требуется ;)
Отлично. Будем кушать слона по кусочкам :)
В чём проблема создать один логгер (идеально — в точке сборки) и передать его в классы, которые в нём нуждаются

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

Есть такая штука, как static. И для неё singleton не требуется ;)

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

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

Теперь поясните как static реализует запрет создания второго экземпляра?

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

  1. Создается экземпляр логера
  2. его инстанс п1 сохряется в каком-то внешнем static member, относительно «классов, которые в нём нуждаются»,
  3. ссылка п2 в последствии инъектится/передается в «классы, которые в нём нуждаются»

Я все верно понял?
Инъектится через передачу интерфейса логера?
Что произойдет, если внутри «классов, которые в нём нуждаются» будет создан свой экземпляр логера и его попробуют присвоить вместо того, который заинъектили?
Я все верно понял?
Да, кроме п.2
Логгер не надо класть в static, его можно создать в куче или на стеке ф-ции main.

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

Что произойдет, если внутри «классов, которые в нём нуждаются» будет создан свой экземпляр логера
Ничего особенного, будет у класса свой экземпляр логгера, если ему надо писать ещё куда-то. Но, вообще, это плохой класс, если он сам создаёт себе логгеры. Сегодня он логгеры создаёт, а завтра будет на 0 делить? Непорядок…

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

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

А что получится, если два экземпляра логера будут писать в один лог-файл а не «куда-то еще»?
Как будет работать запись в один лог-файл, если у вас будет один логер, на несколько потоков?
А что получится, если два экземпляра логера будут писать в один лог-файл а не «куда-то еще»?
Ошибка получится.

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

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

Ошибка получится.

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

Да, в совершенном мире с розовыми пони можно просто отбросить то, что неудобно. Но вот какая беда-горе — мы то живем в реальном мире, и нужно обеспечить запись из многих потоков в один лог-файл. (а-ля EventLog или transaction log БД).
Понятно, что ошибка нам не нужна при записи в лог. И как-то будет решена такая проблема? Есть какие-то идеи и предложения по этому поводу? Возможно, типовые решения?
Проблема не имеет решения в общем случае. Нельзя защититься от того, что код компонента будет написан некорректно и запишет в лог-файл напрямую, используя fopen и fwrite на лог-файле. И плевать ему на все эти синглтоны.
Декларативная безопастность, решает эти вопросы в общем случае, равно но как экслюзивная блокировка доступа. (но это чуть другие и частные варианты)
Ха, эксклюзивная блокировка доступа не решает.

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

И тут есть пара подходов:

1) Компонент пишет в лог только через синглтон «Logger»

2) Компонент пишет в лог только через логгер, который передан ему как зависимость.

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

И тут есть пара подходов:

1) Компонент пишет в лог только через синглтон «Logger»

2) Компонент пишет в лог только через логгер, который передан ему как зависимость.

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


Вот мы почти пришли к общему знаменателю.

Арихитектурно, все-таки нужен какой-то «единый механизм» позволющий предотвращать коллизии при работе с общими ресурсами.

Теперь, давайте рассмотрим чуть детальнее «типовую имплементацию» Singleton
class Logger
{
private static Logger _logger = new Logger();

private Logger() {}

public static Logger GetInstance()
{
return _logger;
}
}

Вопрос: а является ли это класс Singleton? Ответ: и нет и да :)
Почему нет? Очень просто — внутри то то можно насоздавать дофига экземпляров логера:
class Logger
{
private static Logger _logger = new Logger();
.....
private static Logger _loggerN = new Logger();

private Logger() {}

public static Logger GetInstance()
{
return _logger;
}
}

Почему да? Так же просто — границы системы, где доступен один экземпляр, лежит снаружи этого класса.
Если наш Logger будет реализовывать какой-то интерфес — он перестанет быть Singleton в каких-то рамках?
class Logger: ILogger
{
...
private Logger() {}

public static ILogger GetInstance() {...}
}

Очевидно что нет.

А что произойдет, если мы сделаем такую конструкцию?
class DummyBL
{
private ILogger _logger;
public DummyBL(ILogger logger)
{
this._logger = logger;
}
}

ILogger loggerTempRef = Logger.GetInstance();
DummyBL bl1 = new DummyBL (loggerTempRef);
DummyBL blN = new DummyBL (loggerTempRef);

У нас появляется граница системы — инстанс класса DummyBL.
Вроде ничего не нарушено с точки зрения шаблона, только у нас решении появилось две границы системы: «снаружи» Logger и «вложенная» в нее граница DummyBL. Похоже на матрешку.
А что произойдет, если мы уберем одну границу, следующим образом?

class Logger: ILogger
{
...
private public Logger() {}

}

ILogger loggerTempRef = new Logger(); // миграция метода GetInstance() из класса Logger

DummyBL bl1 = new DummyBL (loggerTempRef);
DummyBL blN = new DummyBL (loggerTempRef);

Для самого Logger — ничего не поменялось. Как внутри него можно было плодить экземпляры, так можно и продолждать.
Для самого DummyBL — тоже ничего не поменялось. Внутри него по прежнему нельзя создавать экземпляры Logger.
Остался ли для DummyBL Logger по прежнему Singleton или нет? С точки зрения результата — да.
  1. Граница системы вместо «все, что снаружи Logger», сменилась на «все, что внутри DummyBL»
  2. Фактически, вывернули Singleton «мясом наружу» в данном варианте имплементации, при этом все требования соблюдены: один экземпляр в рамках системы.

Дополнительно — использовали механизм DI, а не «Service Locator» встроенный в начальную имлементацию Singleton.
Ох, каша…
Остался ли для DummyBL Logger по прежнему Singleton или нет?
Некорректная постановка вопроса. DummyBL ничего не знает про Logger.
а не «Service Locator» встроенный в начальную имлементацию Singleton
Singleton в принципе не может быть Service Locator.

Потому что, вызывая Service Locator для некоторого интерфейса ILogger, вызывающий не знает, экземпляр какого класса получит — Logger, или MyLogger, или UberLogger.

Вызывая Logger::getInstance мы обязаны получить Logger, если следовать канонам Singleton.
Некорректная постановка вопроса. DummyBL ничего не знает про Logger.

Если будет вместо интерфейса абстрактный класс, о котором «будет знать», что-то принципиально поменяется?

Вызывая Logger::getInstance мы обязаны получить Logger, если следовать канонам Singleton.

Вроде как шаблон не требует наличия getInstance :)
Поясняющая реализция — предполагает его наличие. Кто-же спорит.
Тот же JavaScript «JavaScript does not have native support for private properties», но Singleton тоже имплементируют :)
Если будет вместо интерфейса абстрактный класс, о котором «будет знать», что-то принципиально поменяется?
Ничего принципиально не меняется. Абстрактный класс не может быть синглтоном :)

Вроде как шаблон не требует наличия getInstance
Не важно, как называется. Главное, что создаётся объект известного класса и объект другого класса быть получен не может.
Вроде как шаблон не требует наличия getInstance

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

Откуда такое ограничение? Вроде как на этот счет нету никаких запретов вроде «если используется наследование, то singleton превращается в тыкву и не важно или singleton наследуется от XXX или от singleton наследуется ZZZ».
Определись уже тогда: для Вас, все таки, критична конкретная имлементация, или нет? Если критична, то с какими оговорками?
Если нет и критерий конечный результат, то остается ключевой, на мой взгляд вопрос: границы «the system» в которых «один экземпляр».
Ключевое условие — единственность экземпляра известного класса.
В результате наследования класс меняется.
Ключевое условие — единственность экземпляра известного класса.
В результате наследования класс меняется.

Ага. Ну… допустим :)
Но тут возникает две другие проблемы :)
  1. Есть такая штука, как взаимодействие систем, написанных на разных язык. В частности MS COM (ActiveX, Ole 1.x/2.x, COM+, DCOM). У нее вообще нет классов, как таковых, а просто куча интерфейсов и сервисная инфраструктура. Но при этом она прекрасно позволяет реализовать Singleton :)
  2. JavaScript до последних спецификации не поддерживал классы, но имплементации были :)
    JavaScript classes
    JavaScript classes introduced in ECMAScript 2015 are primarily syntactical sugar over JavaScript's existing prototype-based inheritance



Очевидно, что шаблон проектирования не должен зависеть от конкретной имплементации и инфраструктуры. Наоборот — варианты имплементации должны реализовывать указанные требования (синтаксическими или ифраструктурными механизмами)
Есть такая штука, как взаимодействие систем, написанных на разных язык. В частности MS COM (ActiveX, Ole 1.x/2.x, COM+, DCOM). У нее вообще нет классов, как таковых, а просто куча интерфейсов и сервисная инфраструктура. Но при этом она прекрасно позволяет реализовать Singleton

Чушь. Синглтона на интерфейсах не бывает по построению. Интерфейс — всего лишь контракт. А за единственность экземпляра в паттерне отвечает реализация. И никакой глобальной точки доступа к конкретному экземпляру в COM нет.
Вы получаете интерфейс, а количество экземпляров реализации определяется не вами а ее автором.
Это прямо противоречит определению синглтона из банды четырех..

Вы получаете интерфейс, а количество экземпляров реализации определяется не вами а ее автором.

Ага… из астрала получаются интерфейсы:)
а количество экземпляров реализации определяется не вами а ее автором.

И где здесь противоречие?
Вообще-то посыл был к тому, что прямое использование «класс» не проходит, как и мплементация «в лоб».
В рамках одного языка, одного приложения — без проблем. Но как только что-то выходит за рамки «эталонной реализации» (осталось понять какая она «эталонная») — тут вылазит куча ограничений и нарушений, и наследуется не правильно, и интерфесы кривые, и хранится не там и не так инстанс, и вообще задача не правильная.
Я кажется понял, что вы хотели сказать: вы не понимаете, как применить архитектурный паттерн IoC + DI в проекте на COM-ах.

Значит, этот подход не везде применим, и поэтому его значение переоценено.
Я кажется понял, что вы хотели сказать: вы не понимаете, как применить архитектурный паттерн IoC + DI в проекте на COM-ах.
Значит, этот подход не везде применим, и поэтому его значение переоценено.

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

Когда ваял на COM тогда и понятия то такого не было IoC и DI. Это уже потом народ узнал, что передача интерфейсов в «другой интерфейс», а не дергать CoCreateInstance «по месту» это называется DI почитав умных книжек.

Но дискуссия то не об этом была.
Критерии оценки — метрики и методология оценки решения какие?
Метрики и метология сравнения решений?
Критерии оценки — метрики и методология оценки на принадлежность «шаблону»?
Эталон решения согласно «шаблону» какой?
Какой check-list на принадлежность «к эталону» и «шаблону»?

Я очень ценю исследование сферических коней в хрустальном замке, очень ценю идеальные решения, но еще больше меня интересует «ценник» на этих решения. Если где-то прибыло, значит где-то убыло. Не бывает чудес.
Я всегда стараюсь пообщаться с горячими стронниками какого-то решения и всегда стараюсь получить ответ на вопрос «А в каких случаях ваше Идеальное Решение не работает? Где границы применения вашего Идеального решения ?» Если сторонник не может внятно дать ответы на этот простой вопрос — значит не до конца понимает, что он «продает» и чем «пользуется», или понимает настолько, что не желает говорить о недостатках и старательно обходит эту тему.
Простой мысленный эксперимент сделать работу с шаренным ресурсом через множество инстансов в нескольких потоках/процессах без каких- либо средство координации и синхронизации оказался не удачным. Все таки глобальные (не принадлежащие контексту исполнения, но доступные в нем) средства синхронизации оказались нужны.
Но дискуссия то не об этом была.
Критерии оценки — метрики и методология оценки решения какие?
Критерии — удобство конфигурирования и использования полученных компонент.

При появлении новых требований один подход требует рефакторинга (выпиливания синглтонов), а другой подход — не требует. Преимущества налицо.
Простой мысленный эксперимент сделать работу с шаренным ресурсом через множество инстансов в нескольких потоках/процессах без каких- либо средство координации и синхронизации оказался не удачным.
Для кого-то неудачным. После десятка итераций: «заявление — опровержение — попытка зайти с другой стороны» все остались при своём мнении.
Все таки глобальные (не принадлежащие контексту исполнения, но доступные в нем) средства синхронизации оказались нужны.
В этом и проблема. Вы не хотите отделить «глобальные» от «доступные». Если средства синхронизации передать как зависимость, они останутся доступными, но пропадут проблемы, присущие глобальности.
Критерии — удобство конфигурирования и использования полученных компонент.

Удобство меряется в деньгах, человеко-часах, строках кода и т.д. в чем-то с цифирками.
При появлении новых требований один подход требует рефакторинга (выпиливания синглтонов), а другой подход — не требует. Преимущества налицо.

Если вы предполагаете, что Singleton это обязательно XXX.GetInstance() внутри, предположим, класса DummyBusinessLogic, то да, это весьма и весьма спорная практика. Но какая религия запрещает XXX.GetInstance() передать снаружи класса ручками или используя контейнер?
Почему плохая практика применения инструмента, переносится и проекцируется на сам инструмент?
Все таки глобальные (не принадлежащие контексту исполнения, но доступные в нем) средства синхронизации оказались нужны.

В этом и проблема. Вы не хотите отделить «глобальные» от «доступные». Если средства синхронизации передать как зависимость, они останутся доступными, но пропадут проблемы, присущие глобальности.

С чего вы это взяли? Я вроде тоже самое и написал. Как хотите так и получайте их. Способ получения зависит от дизайна приложения. Другое дело, что безконтрольное создание их внутри контекста исполнения приводит к невозможности решения задачи, что приводит необходимости выносить создание таких объектов наружу, в более контролируемые и управляемые условия. Желательно не зависящие от человека и контролируемые инструментально.
Если вы предполагаете, что Singleton это обязательно XXX.GetInstance() внутри, предположим, класса DummyBusinessLogic, то да, это весьма и весьма спорная практика. Но какая религия запрещает XXX.GetInstance() передать снаружи класса ручками или используя контейнер?
Интересно. Если контейнер настроен не создавать более одного класса и отдавать ранее созданные, это решение вы можете назвать синглтоном?
Если проходит квалификационные признаки (запрет создания более одного экземпляр в рамках системы), как мы определились ранее, то да — можно рассматривать как вариант имплементации Singleton «мясом наружу», в котором границы системы «все, что снаружи класса», меняются на «все, что внутри контейнера».
Детали имплементации и защиты/запрета другие, результат тот же.
Можно считать, что с императивной имплементации (private construct) в коде меняется на декларативное имплементирование конфигурированием контейнера.
Если проходит квалификационные признаки (запрет создания более одного экземпляр в рамках системы), как мы определились ранее, то да — можно
В таком случае, у нас спор о терминологии.

Ваши оппоненты доказывают, что singleton — т.е. паттерн, в котором ограничение на кол-во экземпляров находится внутри «кирпичика» (класса, модуля, и т.д.) — это плохо.

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

closed.
Согласен. :)
closed

Как вариант, в doDirtyThing() может быть семафор какой-нибудь, который хранится в instance.

Да, но несколькими постами выше автор пренебрежительно упомянул, что есть ещё «семафоры всякие», из чего я понял, что он пробует построить синхронизацию без них, на чистой магии синглтона.
qw1
Да, но несколькими постами выше автор пренебрежительно упомянул, что есть ещё «семафоры всякие», из чего я понял, что он пробует построить синхронизацию без них, на чистой магии синглтона.

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

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

Был предложен вариант (без каких-то дополнительных пояснений) — общий доступ к ресурсу.

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

Какие по вашему свойства у синглтона?
По какием критериям вы определите что это Singleton?

Отсюда и сделан вывод, что для вас синглтон — средство синхронизации,

Синхронизации чего?
Какие по вашему свойства у синглтона?
По какием критериям вы определите что это Singleton?

Смотрите ваши же определения. Они корректны.

Синхронизации чего?
Доступа к разделяемому ресурсу, очевидно.
НЛО прилетело и опубликовало эту надпись здесь
Несмотря на то, что синглтон очевидно и грубо нарушает SRP

Паттерны и принципы SOLID — это инструменты, а не каноны. Им нужно следовать в тех рамках, в которых они оптимальны, и не следовать там, где они неэффективны, а не натягивать любой ценой сову на глобус.
Взять, например, классическую детскую задачку — объект для ведения лога. Типичное решение, это использовать для него синглтон, ну и встроить средства синхронизации, если это требуется. Как вы предлагаете это реализовать с соблюдением SRP во всех трактовках, не ухудшив сопровождаемость кода?

Легко:


  1. Написать класс Lazy<T> (лучше взять готовый).
  2. Завести статическое поле (не в классе логгера) типа Lazy<Logger>.
  3. Завести статическое свойство типа Log (не в классе логгера), геттер которого берет значение из поля.

Итог:


  1. Логгер отвечает за логирование,
  2. Lazy отвечает за единственность экземпляра и ленивую инициализацию,
  3. Статическое свойство отвечает за глобальную доступность экземпляра.

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

Да, но при этом:
1. Мы вместо одной сущности имеем две — логгер и его инициализатор.
2. Где-то вынуждены держать третий класс с этим самым полем/свойством, которым нам, по сути исполняет роль синглтона (то, от чего мы хотели уйти). Или того хуже, дублируем это свойство во всех потребителях лога.
Это как раз ухудшение сопровождаемости, пусть и не критичное, но зато необоснованное. Мы ведь с его помощью не решили ни одной проблемы, но потратили больше времени на разработку, и, вероятно, получили дублирование кода.
  1. Как будто что-то плохое. Инициализатор универсальный, так что вместо 100 сущностей мы имеем 101. Да и готовый скорее всего уже есть (библиотечный).
  2. А здесь вообще все с точностью до наоборот — если уж вы для своего приложения решили иметь глобальные сервисы, то им самое место в отдельном классе. Правда лично я предпочитаю ничего глобального не иметь вообще.

Мы ведь с его помощью не решили ни одной проблемы,

Правда?


  1. Сам логгер стал проще (можно просто взять готовый из любой подходящей библиотеки)
  2. Логгеров теперь можно иметь столько, сколько нужно и переиспользовать любым удобным способом.
  3. Ленивая инициализация и контроль числа экземпляров сделаны один раз и навсегда.
  4. Ссылка на глобальный логгер находится на правильном уровне абстракции и никому не мешает.

Зачем вы упорствуете в защите заведомо плохого решения?

Зачем вы упорствуете в защите заведомо плохого решения?

Потому что я не считаю его заведомо плохим, наоборот, я абсолютно искренне считаю плохим ваше решение. Вот моя точка зрения:


  1. Выше всех паттернов лежит подзабытый принцип KISS. Если какое-то решение делает то же самое, но с бОльшим количеством абстракций, оно хуже.
  2. Абстракции, которые делаются "на всякий случай" (например, логгеров, которые можно иметь столько, сколько нужно, если на самом деле нужен один), не нужны.
  3. Если я решил в приложении использовать глобальные сервисы, то наверное да, я так и сделаю. Но это классическая ошибка архитектора — решать частную задачу с помощью общего решения на тот случай "когда проект будет расти, развиваться, и нам это понадобится", при этом совершенно не учитывая, будет ли он в том направлении расти, и понадобится ли это, и сложно ли будет не сейчас, а в будущем внести эти изменения, если они понадобятся.
    Речь идет ведь об одном конкретном глобальном объекте. Если их пять, десять, сто, я с вами бы согласился — там будет разумно использовать и Lazy-контейнер, и некий класс Globals. Но в случае решения одной конкретной задачи "где разместить логгер", вы неправы :)
  1. Вы даете очень далекую от исходного смысла трактовку принципа KISS. Вы делаете каждый свой класс-синглтон сложнее, причем за счет копипасты. Это как минимум не проще, чем вынос контроля количества экземпляров в отдельный класс, который, к тому же, в 99% случаев не нужно писать самому.


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


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

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

Вы делаете каждый свой класс-синглтон сложнее, причем за счет копипасты.

Не могли бы вы пояснить а «копипасты» чего? Откуда и куда? Сколько не пользовал Singleton ничего никуда не копировал. Может мы о разных говорим?

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


Можете дать ваш пример кода для синглтона?

Не будем далеко зазить в дебри. Возмем классику Class diagram exemplifying the singleton pattern.
на куче языков и на любой вкус.

Вы можете дать пример именно вашего кода, в соответствии с вашей же репликой ниже?


Сколько не пользовал Singleton ничего никуда не копировал.

Ссылка на диаграмму классов из книги, которую ваш собеседник заведомо читал — это совсем не то, не так ли?

Довольно смело утверждать, что Singleton нарушает SRP. Холиварный вопрос)) Singleton объект ограничивает кол-во созданных экземпляров себя, а это можно рассматривать, как часть бизнес-логики объекта. Следовательно, нарушения SRP нет.

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

Бизнес логика должна быть в модели, потому как модель, как странно бы это не звучало, модель бизнес логики.
Это не класс в папочке "models", а более абстрактная вещь.

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

Утверждение «Обнаружить такое нарушение достаточно просто, по наличию любых методов в объекте модели» звучит слишком сильно. Я знавал джунов, которые создавали статический
PersonHelper.getFullName(Person p)
и объясняли тем, что что метод
getFullName() { return getFirstName() + getLastName();} 
в модели незаконен согласно фразе из умного_источника.

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

При этом я абсолютно согласен, что бизнес-логика в виде какого-нибудь SalaryCalculator должна быть отделена от Person и это применение SRP
Есть 10 типов людей — которые не видят разницы между процедурным и ооп подходами. А также 10 типов людей которые не видят разницы между функциональным и процедурным подходами.
Не сказал бы, что сам по себе синглтон нарушает SRP. Синглтон — скорее свойства типа объекта, не относящееся напрямую к его задаче.

Синглтон легко написать как бы без нарушения SRP — достаточно разбить на условный Foo, реализующий бизнес-логику, и FooSingleton, реализующий доступ к единственному aFoo.


SRP будет соблюден, но ключевые проблемы останутся.

После 3-го прочтения, я кажется понял, что не так — возникла путаница в терминологии. Больщинство проблем, что вы описали, относятся к наивной реализации Singleton.getInstance(), которая действительно увеличивает связность и тп. Но это лишь одна из реализация синглтона, ниже вы указываете, что тот же IoC фреймворк создает тоже объект-Singleton, и логика инициализации вынесена из самого объекта. Озаглавливать это "синглтон нарушает SRP", как то не правильно. Наиваня реализация "нарушает" — согласен.


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

Я бы не тверждал это с такой уверенностью. Хотя на сегодня паттерн Repository (https://msdn.microsoft.com/en-us/library/ff649690.aspx) и является нормой по мнению больниства, существует и Active Record (https://en.wikipedia.org/wiki/Active_record_pattern), активно используемый, даже не смотря на критику по SRP. Тот же Repository страдает от того, что объекты не вляются объектами а лишь структурами, в результате чего нарушаются принципы самого ООП. Что лучше нарушать SRP или Объектный подход это вопрос. О преимуществах Active Record можете посмотреть холиварный доклад на JPoint 2016 https://www.youtube.com/watch?v=ckjAWXJWZEY и комментарий Алименкова, сравнивающий два подхода (https://youtu.be/ckjAWXJWZEY?t=1943) довольно наглядно.

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

Взять хотябы мой пример. Писали систему извлечения контента по URL, причем для самого извлечения после ресерча решили воспользоваться готовой библиотекой — https://github.com/grangier/python-goose Приверно в 70-80% случаях работает отлично. Мы же писали обертку над ним, хендлер очередей, менеджер парсинга, кастомные парсеры для особо «важных» сайтов или сервисов с API

Так вот, в итоге мы пришли к тому, что простая логика, в которой Гусь получает URL и сам делает запрос не SRP. Нам пришлось написать отдельный микросервис именно для этого этапа с запросом, чтобы решить 6 или 7 проблем.Причем, это были серьезные проблемы, которые угрожали существованию проекта. Как Anti-DDOS система. Первая версия нашего приложения получав 1000 ссылок с одного сайта с радостью отправляла 1000 запросов в секунду и наш клиент получил очень много абуз от недовольных владельцев сайтов.

Я это к тому, что только опыт позволит следовать этому принципу, говорить же о нем, ИМХО, бесполезно
Какой-то очень сомнительный аргумент. Сначала программистам говорят «не надо создавать divine классы». Потом к этому добавляют SRP и только потом отпускают ломать дрова на собственном опыте. Потому как, в противном случае, человек будет бродить в потьмах. Рано или поздно он для себя откроет те истины, которые, как он обнаружит, были раскрыты и задекларированы задолго до него, но к чему затягивать это «рано или поздно»?
Сначала программистам говорят «не надо создавать divine классы». Потом к этому добавляют SRP и только потом отпускают ломать дрова на собственном опыте. Потому как, в противном случае, человек будет бродить в потьмах. Рано или поздно он для себя откроет те истины, которые, как он обнаружит, были раскрыты и задекларированы задолго до него, но к чему затягивать это «рано или поздно»?

Пока это все выглядит «О! мы тут прикольную штуку наваяли!» и куча обезьянок ломанулась тупо копировать.
Возможно, есть простое объяснение: инженерная практика предполагает расчеты для выбора оптимального решения в поставленных рамках.
Все эти OOP, SOLID — это просто "best practice". Это говорит только о том, что указанные практики приносят пользу. Единственный вопрос — цена реализации и цена сопровождения.
Если не учитывается вопрос цены, то это все просто «обезъянка видит — обезъянка делает».

Формальные признаки нарушения SRP:

Формальные признаки, предполагают объективную оценку и наличие метрик.
Давайте метриками оценивать «Большее количество публичных методов класса» или «разрастание размера класса или метода.»

И дайте четкую метрику для понятия «Single Responsibility Principle». Атомараная функция? Доменная область? Бизнес-процесс?
После этого можно будет четко сказать — нарушено или нет.
А «может свидетельствовать» это все фигня.
Мне не в первый раз доводится слышать «пусть понимают, а не копируют». И не в первый раз я с этим не согласен. Люди так учатся и это нормально. Дети произносят слова, но могут даже не понимать их настоящего смысла(словообразование, заимствованность, эмпирическая составляющая). И это может сохраняться до самого что ни на есть старческого возраста.

Единственный вопрос — цена реализации и цена сопровождения.
Если не учитывается вопрос цены, то это все просто «обезъянка видит — обезъянка делает».

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

И дайте четкую метрику для понятия «Single Responsibility Principle». Атомараная функция? Доменная область? Бизнес-процесс?
После этого можно будет четко сказать — нарушено или нет.
А «может свидетельствовать» это все фигня.


В случае с SOLID, все принципы, кроме I, накладывают границы, а не дают какие-то рекомендации. Выход за эти границы — есть нарушение принципа. Это и есть те самые «четкие требования».
Вот то, что Вы и Ваша команда понимают под границами принципов, и является «четкой метрикой». Разумеется, при условии того, о чем я сказал в самом верху: каждый должен получить базу и на основе этой базы уже формировать командное восприятие.
Мне не в первый раз доводится слышать «пусть понимают, а не копируют».

Никто не запрещает копировать. Нужно просто понимать что ты копируешь и почему.

. Дети произносят слова, но могут даже не понимать их настоящего смысла(

Еще как понимаеют. Для этого есть положительная и отрицательная обратная связь в виде конфетки-котлетки или подзатыльника :)

Вот то, что Вы и Ваша команда понимают под границами принципов, и является «четкой метрикой».

Четкая метрика — выражается цифрами и метологиями измерений, она воспроизводима.
В противном случае называется субъективная оценка или, в лучшем случае, эмпирическая оценка. Принцип не может быть «общим для команды», иначе это религия или карго культ.
Так вот, в итоге мы пришли к тому, что простая логика, в которой Гусь получает URL и сам делает запрос не SRP. Нам пришлось написать отдельный микросервис именно для этого этапа с запросом, чтобы решить 6 или 7 проблем.

Ради любопытства, проблемы решились потому, что вы стали следовать SRP, или потому, что вы баги в коде пофиксили?
Хватит уже наезжать на синглтоны. Рабочая лошадка, успешно живущая уже десятилетия в тысячах проектов и нормально делающая своё дело. Конечно, и синглтон можно написать по-дурацки, и наделать их можно по молодости 100 штук, но это всё крайности, не связанные с полезностью (в ограниченных пределах) самого паттерна.
Решение описанных проблем — отказаться от использования нотификаций в пользу более удобных механизмов.

а можно промер таких механизмов, да что б оставить слабую связаность компонентов?
Например Dependency Injection.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации