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

Первый способ: Singleton


Singleton — подразумевает, что помимо своих основных обязанностей класс занимается еще и контролированием количества своих экземпляров, чем нарушает Single Responsibility Principle.

Очень просто создать Singleton. Достаточно скопировать из википедии пару строк кода в вашу любимую IDE. Singleton есть везде, не нужно заморачиваться с передачей объекта в нужное место и управлять памятью тоже не обязательно, экземпляр один, к тому же он вечный. Эта легкость, возможно, и является причиной применения Singleton-а не по назначению.

Singleton повышает связность кода


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

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

Экземпляр Singleton-а невозможно подменить без танца с бубном


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

Singleton может хранить свое состояние


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

Есть более безопасные порождающие паттерны


Повышение связности кода и невозможность подмены его экземпляра успешно решает IoC. Реализация этого принципа, например с помощью Dependency Injection, забирает обязанность контролирования количества экземпляров класса и делает зависимости более явными.

Несмотря на все описанные выше ужасы, есть места, где уместно использовать Singleton


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

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

Второй способ: смешение архитектурных слоев


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

Бизнес-логика в модели.


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

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

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

Неправильное использование паттернов MVC, MVP, MVVM


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


Но у каждого из этих паттернов один общий недостаток — разрастание Controller-а, Presenter-а или View-Model-и. Суть проблемы кроется в неправильном понимании компонента View и компонента хранящего бизнес-логику (Controller, Presenter или View-Model).

View должна содержать логику для отображения пользовательского интерфейса Controller, Presenter или View-Model должны содержать бизнес-логику. Отдельных слов заслуживает MVC iOS SDK навязывает использование MVC. Но UIViewController не является MVC-шным Controller-ом, так как в большинстве случаев он содержит логику для отображения пользовательского интерфейса.
Этот факт делает практически невозможным реализацию правильного MVC. В лучшем случае UIViewController становится частью View, бизнес логика выносится в другие слои.

С другой стороны, после отделения логики, необходимой для отображения интерфейса, в Controller-е, Presenter-е или View-Model-и все равно может остаться слишком много кода. Это очень часто связано с тем, что для одного экрана создается один объект, содержащий бизнес-логику, при этом он может реализовывать несколько пользовательских сценариев, что опять нарушает SRP.

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

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

Решением проблемы “разрастающегося контроллера” будет грамотное использование архитектурных паттернов. Если придерживаться SRP при написании кода и выносить из Controller-а, Presenter-а или View-Model-и весь код, не относящийся к его основной ответственности, логика станет прозрачнее, пользовательские сценарии понятнее, и их будет проще читать.

Третий способ: NSNotificationCenter


Нотификации в iOS — отличный способ связать все со всем!

NSNotificationCenter является частным, но достаточно ярким представителем Singleton-а. Несмотря на то, что нотификации — паттерн взаимодействия (Communication Pattern), а Singleton — порождающий паттерн (Creational Pattern), нотификации сохранюет все недостатки Singleton-а.

Начиная обсервить нотификацию, не связанную с основными обязанностями класса мы нарушаем SRP.

Основные проблемы, возникающие при использовании нотификаций:

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

Если нотификация отправлена, мы уже не можем ни перехватить, ни подменить ее.

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

Решение описанных проблем — отказаться от использования нотификаций в пользу более удобных механизмов.

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

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

  • Одним из самых заметных признаков является разрастание размера класса или метода.
  • Наследование класса от большего количества протоколов (при условии соблюдения ISP).
  • Использование Singleton-ов может свидетельствовать о нарушении SRP.
  • Передача информации об изменении состояния конкретного объекта с использованием NSNotificationCenter (можно нотификациями сообщать об изменении глобального состояния)
  • Скапливание в объектах утилитных методов.
  • Большее количество публичных методов класса, также может свидетельствовать о нарушении SRP.
  • Большее количество приватных методов, которые можно разбить на группы. В этом случае каждая группа, скорее всего, имеет свою зону ответственности.
Поделиться с друзьями
-->

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


  1. crea7or
    03.06.2017 18:02
    +2

    Ради чего всё это?


  1. oxidmod
    03.06.2017 18:53
    +6

    Бизнес логика в контроллере… Как интересно.


  1. Lofer
    03.06.2017 19:01
    +4

    что помимо своих основных обязанностей класс занимается еще и контролированием количества своих экземпляров, чем нарушает 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

    Написано «отдельная часть». Где написано, клас должен поддерживать одну функцию?
    Где тут нарушение, если класс заведует тем, для чего предназначен?


    1. Antervis
      04.06.2017 08:35

      over a single part of the functionality


      1. Nahrimet
        04.06.2017 14:14

        Это аргумент в пользу того, что сказал Lofer.


  1. DrPass
    03.06.2017 19:56
    +11

    Singleton — подразумевает, что помимо своих основных обязанностей класс занимается еще и контролированием количества своих экземпляров, чем нарушает Single Responsibility Principle.

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


    1. Bonart
      06.06.2017 11:09
      +2

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


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


      1. Lofer
        06.06.2017 12:11

        Страшно далека от любой другой функциональности класса.

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

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

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

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

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

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

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


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


        1. Bonart
          06.06.2017 12:58

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

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


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

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


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

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


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

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


          1. Lofer
            06.06.2017 18:26

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

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

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

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

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

            ну ну…


            1. Bonart
              06.06.2017 18:50

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

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


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

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


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

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


              1. Lofer
                06.06.2017 19:37

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

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


                1. Bonart
                  07.06.2017 09:56
                  +1

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


                  1. Lofer
                    07.06.2017 13:07

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

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


                    1. Bonart
                      07.06.2017 13:42

                      В приложении у вас получается: куча интерфейсов, наличие публичного конструктора классов, иначе DI механизм просто не заработает, обязательное использование DI.

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


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

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


                      1. Lofer
                        07.06.2017 14:06

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

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

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


                        1. Bonart
                          07.06.2017 15:29
                          +1

                          Т.е никаких технических решение вы не предлагаете

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


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

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


                          1. Lofer
                            07.06.2017 15:38

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

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

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

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

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


                            1. oxidmod
                              07.06.2017 15:46
                              +2

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


                            1. Bonart
                              07.06.2017 15:53

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

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


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

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


                              1. Lofer
                                07.06.2017 16:34

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

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

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

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

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

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

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

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

                                Это тоже решение. Оно имеет право на жизнь.


                                1. oxidmod
                                  07.06.2017 16:58

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


                                  Либо вы, либо я что-то не понимаю. Как вы накосячите с new, если не знаете названия класса? Не, естественно если вы задались целью накосячить, то способы всегда найдутся. Но весь смысл DI в том, чтобы избавить клиентский код от отвественности за инстанциирование объектов. Это сразу делает все зависимости явными, позволяет легко и просто тестировать логику вашего приложения, а не то что создаются правильные объекты.


                                  1. Lofer
                                    07.06.2017 17:44

                                    Но весь смысл 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»


                                    1. oxidmod
                                      07.06.2017 17:51

                                      Ну вот клиентскому коду не нужно знать о ClassType. Ему нужен ITargetTypeInterface и точка.


                                      1. Lofer
                                        07.06.2017 18:12

                                        Ну вот клиентскому коду не нужно

                                        Есть разница между «не нужно знать» и «не иметь возможности»
                                        Весь мир состоит только в клиентском коде или есть другие слои? :)


                                        1. oxidmod
                                          07.06.2017 18:34

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


                                          1. Lofer
                                            07.06.2017 19:10

                                            Конкретно за new отвечает контейнер и код, который его конфигурирует

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


                                            1. oxidmod
                                              07.06.2017 19:24

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

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


                                              1. Lofer
                                                07.06.2017 20:47

                                                И все ради чего? Ради чего писать код синглтона?

                                                Задачи противоположные по смыслу:
                                                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 свет клином сошелся и если его не использовать, то небо на замлю упадет?


                                                1. oxidmod
                                                  07.06.2017 23:42

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


                                                1. Bonart
                                                  08.06.2017 10:27

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

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


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

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


                                            1. Bonart
                                              08.06.2017 10:35
                                              +1

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


                                1. Bonart
                                  08.06.2017 10:21

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

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


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

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


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

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


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


                                  1. Lofer
                                    08.06.2017 12:11

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

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


                                    1. Bonart
                                      08.06.2017 15:56

                                      Мы подразумеваем одно и то-же под 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 — типичный антипаттерн, так использовать контейнеры все равно что забивать гвозди микроскопом.


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

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


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

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


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

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


                                      1. Lofer
                                        08.06.2017 17:20

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

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


                                        1. Bonart
                                          12.06.2017 14:05

                                          Я такое никогда не утверждал, возможно, вы не корректно поняли.

                                          Неужели?


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

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


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

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


                                      1. qw1
                                        08.06.2017 20:41

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

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

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

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

                                        Хотя… тут, похоже, ключевое слово — своих зависимостей


                                        1. qw1
                                          08.06.2017 20:46

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

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


                                          1. qw1
                                            08.06.2017 20:55

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


                                            1. Bonart
                                              12.06.2017 13:47

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


                                              1. qw1
                                                12.06.2017 15:12

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

                                                Кодировать-то надо намного больше. Вместо прежнего

                                                DocViewModel vm = container.Resove<DocViewModel>();
                                                workplace.ShowModal(vm);
                                                будет похожее
                                                IDocViewModel vm = factory.Create();
                                                workplace.ShowModal(vm);

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

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

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


                                          1. Lofer
                                            09.06.2017 12:08

                                            Либо тут надо городить фабрику на ровном месте,

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


                                            1. qw1
                                              10.06.2017 18:53

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


                                              1. Lofer
                                                10.06.2017 19:59

                                                Увы. С чудесами сложно в создании объектов:

                                                • создать самому — что «не правильно»
                                                • сделать фабрику, которая будет создавать объекты, которую тоже как-то надо создать и сделать доступной.
                                                • фабрика, будет по факту делать то-же что и контейнер, почему-бы не заинъектить контейнер.


                                                Осталость выбрать между «не правильными» вариантами :)


                                                1. qw1
                                                  10.06.2017 21:17

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

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

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

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

                                                  Вроде, все овцы сыты?


                                                  1. Lofer
                                                    10.06.2017 23:55

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

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


                                                    1. oxidmod
                                                      11.06.2017 10:36

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


                                                      1. Lofer
                                                        11.06.2017 11:00

                                                        Банально по конструктору не видно что конкретно нужно.

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

                                                        Делается что-то вроде:
                                                        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. oxidmod
                                                          11.06.2017 13:47

                                                          Вот во втором случае видна зависимость от фабрики. А в первом от контейнера. Что класс достанет с контейнера ниразу не ясно. Там может быть куча логики зависимой от кучи объектов в контейнере.

                                                          Зы. Забыл табличку «сарказм» возле глобалов


                                                          1. Lofer
                                                            11.06.2017 20:07

                                                            Вопросов нет. В целом согласен.


                                                    1. Bonart
                                                      12.06.2017 14:34

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

                                                      Фабрика:


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

                                                      Контейнер:


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

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


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

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


                                            1. Bonart
                                              12.06.2017 13:50

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


                                              1. Lofer
                                                12.06.2017 16:28

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

                                                Рассматривалось в контекте одноразововой ситуации «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.


                                                1. qw1
                                                  12.06.2017 19:54

                                                  Вас ранее просили дать конкретный пример, когда Singleton хорошо подходит.

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

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

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


                                                  1. Lofer
                                                    12.06.2017 21:40

                                                    Вас ранее просили дать конкретный пример, когда 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 это хорошо? Да хорошо.
                                                    Хреновый дизайн плохо? да плохо.


                                                    1. qw1
                                                      13.06.2017 00:58

                                                      Попробуйте реализацию журналирования и странзакциоонности для файловой системы, для примера.
                                                      Отличный анти-пример :)

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

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

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

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

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

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


                                                      1. Lofer
                                                        13.06.2017 12:40

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

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

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

                                                        Вопрос в поставленной задаче и проектном ее решении, а не в конкретном варианте имплементации и тем более не корректном применении. Осталось только кривые шрифты приписать Singleton


                                                        1. oxidmod
                                                          13.06.2017 12:58
                                                          +1

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

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


                                                          1. Lofer
                                                            13.06.2017 15:14

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

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

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

                                                            Есть такое. Но у БД их сколько?


                                                            1. Lofer
                                                              13.06.2017 15:26

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


                                                              1. qw1
                                                                13.06.2017 16:37

                                                                Тут я совсем потерялся, чем синглтон в принципе может помочь.
                                                                Кто мешает «гадить всем сразу», вызывая в разных потоках
                                                                Singleton::getInstance()->doDirtyThing()


                                                            1. oxidmod
                                                              13.06.2017 16:45

                                                              Может быть и больше одного. Чтение и запись под разными юзерами с разными наборами прав на уровне бд.


      1. DrPass
        06.06.2017 12:21

        Несмотря на то, что синглтон очевидно и грубо нарушает SRP

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


        1. Bonart
          06.06.2017 13:12

          Легко:


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

          Итог:


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

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


          1. DrPass
            06.06.2017 13:43

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


            1. Bonart
              06.06.2017 14:07
              +1

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

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

              Правда?


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

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


              1. DrPass
                06.06.2017 14:54

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

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


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


                1. Bonart
                  06.06.2017 15:42

                  1. Вы даете очень далекую от исходного смысла трактовку принципа KISS. Вы делаете каждый свой класс-синглтон сложнее, причем за счет копипасты. Это как минимум не проще, чем вынос контроля количества экземпляров в отдельный класс, который, к тому же, в 99% случаев не нужно писать самому.


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


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

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


                  1. Lofer
                    06.06.2017 18:30

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

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


                    1. Bonart
                      06.06.2017 18:53
                      +1

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


                      1. Lofer
                        06.06.2017 19:43


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

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


                        1. Bonart
                          07.06.2017 10:02

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


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

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


  1. crea7or
    03.06.2017 20:38
    +3


  1. ivanzaruba
    03.06.2017 22:59
    +2

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

    Например,

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


  1. Metus
    03.06.2017 23:13
    +1

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


    1. mnv
      03.06.2017 23:54
      +1

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


    1. ads83
      04.06.2017 22:33
      +3

      Утверждение «Обнаружить такое нарушение достаточно просто, по наличию любых методов в объекте модели» звучит слишком сильно. Я знавал джунов, которые создавали статический

      PersonHelper.getFullName(Person p)
      и объясняли тем, что что метод
      getFullName() { return getFirstName() + getLastName();} 
      в модели незаконен согласно фразе из умного_источника.

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

      При этом я абсолютно согласен, что бизнес-логика в виде какого-нибудь SalaryCalculator должна быть отделена от Person и это применение SRP


  1. Antervis
    04.06.2017 08:39

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


  1. lalaki
    04.06.2017 11:28
    +2

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


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


  1. Rabajaba
    04.06.2017 14:14
    -1

    После 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) довольно наглядно.


  1. c3gdlk
    04.06.2017 22:32

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

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

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

    Я это к тому, что только опыт позволит следовать этому принципу, говорить же о нем, ИМХО, бесполезно


    1. Nahrimet
      05.06.2017 01:32

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


      1. Lofer
        05.06.2017 02:39

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

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

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

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

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


        1. Nahrimet
          05.06.2017 14:26

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

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

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

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


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


          1. Lofer
            05.06.2017 15:06

            Мне не в первый раз доводится слышать «пусть понимают, а не копируют».

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

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

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

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

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


    1. DrPass
      05.06.2017 18:26

      Так вот, в итоге мы пришли к тому, что простая логика, в которой Гусь получает URL и сам делает запрос не SRP. Нам пришлось написать отдельный микросервис именно для этого этапа с запросом, чтобы решить 6 или 7 проблем.

      Ради любопытства, проблемы решились потому, что вы стали следовать SRP, или потому, что вы баги в коде пофиксили?


  1. DrPass
    04.06.2017 23:07

    del


  1. tangro
    05.06.2017 16:38
    -1

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


  1. vitvad
    06.06.2017 00:55

    Решение описанных проблем — отказаться от использования нотификаций в пользу более удобных механизмов.

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


    1. spolispastom
      06.06.2017 19:07

      Например Dependency Injection.