Singleton для чего нужен
Реализация Singleton в JAVA
В этой статье я хочу затронуть тему одного из наиболее распространенных паттернов объектно-ориентированного программирования – Singleton. Но в данном случае я не буду описывать преимущества/недостатки и области применения этого паттерна, а попытаюсь изложить свой взгляд на его имплементацию в JAVA.
Общие сведения
Паттерн Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Область применения
1.) В системе должно существовать не более одного экземпляра заданного класса.
2.) Экземпляр должен быть легко доступен для всех клиентов данного класса.
3.) Создание объекта on demand, то есть, когда он понадобится первый раз, а не во время инициализации системы.
Реализация (JAVA):
На данный момент существуют несколько вариантов реализации со своими недостатками и преимуществами. В них мы и попробуем сейчас разобраться.
Вариант первый – самый простой, который приходит в голову сразу после понимания проблемы.
У этого решения есть единственный недостаток – оно не работает в многопоточной среде и поэтому не подходит в большинстве случаев. Решение подходит исключительно для однопоточных приложений.
Не беда, скажите вы, и предложите следующее решение.
Вариант второй:
И вы будете правы, так как проблему многопоточности мы решили, но потеряли две важные вещи:
1. Ленивую инициализацию (Объект instance будет создан classloader-ом во время инициализации класса)
2. Отсутствует возможность обработки исключительных ситуаций(exceptions) во время вызова конструктора.
Решение подходит для многопоточных приложений, при условии отсутствия опасности возникновения исключительных ситуаций в конструкторе и отсутствии необходимости ленивой инициализации.
Далее возникают 2 варианта решения.
1.) Использование внутреннего класса(решение Била Пью(Bill Pugh) “Initialization on Demand Holder”).
2.) Использование синхронизации.
Вариант третий:
“Initialization on Demand Holder”
В данном случае мы полностью решили проблему ленивой инициализации – объект инициализируется при первом вызове метода getInstance(). Но у нас осталась проблема с обработкой исключительных ситуаций в конструкторе. Так что, если конструктор класса не вызывает опасений создания исключительных ситуаций, то смело можно использовать этот метод.
Синхронизация
Этой части я хотел бы уделить особое внимание. Можно было бы подойти к данному вопросу с заголовком «synchronized – мифы и реальность».
И так, самый прямолинейный метод.
Вариант четвертый:
У этого варианта есть только один недостаток. Синхронизация полезна только один раз, при первом обращении к getInstance(), после этого каждый раз, при обращении этому методу, синхронизация просто забирает время. Что можно сказать по этому поводу? Ну, во-первых, если вызов getInstance() не происходит достаточно часто (что значит «достаточно часто» решать вам), то этот метод имеет преимущество перед остальными – прост, понятен, лениво инициализируется, дает возможность обрабатывать исключительные ситуации в конструкторе. А во-вторых, синхронизация в Java перестала быть обременительно медленной настолько, насколько ее боятся. Ну что еще для счастья надо?
Теперь рассмотрим вариант синхронизированного решения, в котором попытаемся решить проблему, возникшую в предыдущем варианте.
Наиболее распространенный способ — «Double-Checked Locking». В своем оригинальном варианте:
Не работает! Почему? Это отдельная тема, но для интересующихся могу посоветовать прочитать эту статью http://www.cs.umd.edu/
Но не надо совсем отчаиваться, в JAVA 5 проблему решили, используя модификатор volatile. На данный момент решение выглядит так:
Вариант пятый:
Не смотря на то, что этот вариант выглядит как идеальное решение, использовать его не рекомендуется т.к. товарищ Allen Holub заметил, что использование volatile модификатора может привести к проблемам производительности на мультипроцессорных системах. Но решать все же вам.
Вот, в общем-то, и все распространенные варианты имплементаций данного паттерна. Но, на этом не заканчиваются подводные камни Singleton-а. Существуют еще несколько моментов, которые нужно учитывать во время проектирования того или иного приложения, использующего Singleton.
Подводные камни
1. Наследование
В подавляющем большинстве случаев в Singleton классах наследование не нужно и, более того, излишне и является следствием over-design. Да и реализация наследования имеет определенные сложности, учитывая, что и сам instance и метод getInstance() статические.
Поэтому, я рекомендую использовать модификатор final и запретить наследование данного класса, если нет особой необходимости в обратном.
2. Две и более виртуальных машины
Каждая виртуальная машина создает свою копию Singleton объекта. И хотя на первый взгляд это выглядит очевидным, во многих распределенных системах, таких как EJB, JINI и RMI все не так просто. Когда промежуточные уровни скрывают (делают прозрачными) распределенные технологи, бывает трудно сказать, где и когда инициализирован объект.
3.Различные Class Loader-ы
Когда 2 class loader-а загружают класс, каждый из них может создать свою копию Singleton-а(в тех случаях, когда instance инициализируется class loader-ом ). Это особенно актуально в использовании сервлетов(servlet), так как в некоторых имплементациях серверов приложений(application server) каждый сервлет имеет свой class loader.
Существует еще ряд проблем, которые менее актуальны (такие как технология reflection и имплементация интерфейсов Cloneable и Serializable), и не будут мною рассмотрены в силу своей экзотичности в сфере применения Singleton классов. Но, в любом случае, с радостью отвечу на любые вопросы к этому материалу.
Вот, в общем-то, и все ключевые моменты, которые я хотел осветить в данной статье. Осталось лишь заметить, что данный материал не претендует быть истиной в последней инстанции и допускает существование точек зрения, отличающихся от точки зрения автора. И даже более того, любые замечания приветствуются и будут прияты к сведению.
Подводные камни Singleton: почему самый известный шаблон проектирования нужно использовать с осторожностью
Авторизуйтесь
Подводные камни Singleton: почему самый известный шаблон проектирования нужно использовать с осторожностью
Паттерн «Одиночка» — пожалуй, самый известный паттерн проектирования. Тем не менее, он не лишен недостатков, поэтому некоторые программисты (например, Егор Бугаенко) считают его антипаттерном. Разбираемся в том, какие же подводные камни таятся в Singleton’е.
Определение паттерна
Само описание паттерна достаточно простое — класс должен гарантированно иметь лишь один объект, и к этому объекту должен быть предоставлен глобальный доступ. Скорее всего, причина его популярности как раз и кроется в этой простоте — всего лишь один класс, ничего сложного. Это, наверное, самый простой для изучения и реализации паттерн. Если вы встретите человека, который только что узнал о существовании паттернов проектирования, можете быть уверены, что он уже знает про Singleton. Проблема заключается в том, что когда из инструментов у вас есть только молоток, всё вокруг выглядит как гвозди. Из-за этого «Одиночкой» часто злоупотребляют.
Простейшая реализация
Как уже говорилось выше, в этом нет ничего сложного:
Принцип единственной обязанности
В объектно-ориентированном программировании существует правило хорошего тона — «Принцип едиственной обязанности» (Single Responsibility Principle, первая буква в аббревиатуре SOLID). Согласно этому правилу, каждый класс должен отвечать лишь за один какой-то аспект. Совершенно очевидно, что любой Singleton-класс отвечает сразу за две вещи: за то, что класс имеет лишь один объект, и за реализацию того, для чего этот класс вообще был создан.
Принцип единственной обязанности был создан не просто так — если класс отвечает за несколько действий, то, внося изменения в один аспект поведения класса, можно затронуть и другой, что может сильно усложнить разработку. Так же разработку усложняет тот факт, что переиспользование (reusability) класса практически невозможно. Поэтому хорошим шагом было бы, во-первых, вынести отслеживание того, является ли экземпляр класса единственным, из класса куда-либо во вне, а во-вторых, сделать так, чтобы у класса, в зависимости от контекста, появилась возможность перестать быть Singleton’ом, что позволило бы использовать его в разных ситуациях, в зависимости от необходимости (т.е. с одним экземпляром, с неограниченным количество экземпляров, с ограниченным набором экземпляров и так далее).
Тестирование
Один из главных минусов паттерна «Одиночка» — он сильно затрудняет юнит-тестирование. «Одиночка» привносит в программу глобальное состояние, поэтому вы не можете просто взять и изолировать классы, которые полагаются на Singleton. Поэтому, если вы хотите протестировать какой-то класс, то вы обязаны вместе с ним тестировать и Singleton, но это ещё полбеды. Состояние «Одиночки» может меняться, что порождает следующие проблемы:
На эту тему есть отличный доклад с «Google Tech Talks»:
Скрытые зависимости
Обычно, если классу нужно что-то для работы, это сразу понятно из его методов и конструкторов. Когда очевидно, какие зависимости есть у класса, гораздо проще их предоставить. Более того, в таком случае вы можете использовать вместо реально необходимых зависимостей заглушки для тестирования. Если же класс использует Singleton, это может быть совершенно не очевидно. Всё становится гораздо хуже, если экземпляру класса для работы необходима определённая инициализация (например, вызов метода init(. ) или вроде того). Ещё хуже, если у вас существует несколько Singleton’ов, которые должны быть созданы и инициализированы в определённом порядке.
Загрузчик класса
Если говорить о Java, то обеспечение существования лишь одного экземпляра класса, которое так необходимо для Singleton, становится всё сложнее. Проблема в том, что классическая реализация не проверяет, существует ли один экземпляр на JVM, он лишь удостоверяется, что существует один экземпляр на classloader. Если вы пишете небольшое клиентское приложение, в котором используется лишь один classloader, то никаких проблем не возникнет. Однако если вы используете несколько загрузчиков класса или ваше приложение должно работать на сервере (где может быть запущено несколько экземпляров приложения в разных загрузчиках классов), то всё становится очень печально.
Десериализация
Ещё один интересный момент заключается в том, что на самом деле стандартная реализация Singleton не запрещает создавать новые объекты. Она запрещает создавать новые объекты через конструктор. А ведь существуют и другие способы создать экземпляр класса, и один из них — сериализация и десериализация. Полной защиты от намеренного создания второго экземпляра Singleton’а можно добиться только с помощью использования enum’а с единственным состоянием, но это — неоправданное злоупотребление возможностями языка, ведь очевидно, что enum был придуман не для этого.
Потоконебезопасность
Один из популярных вариантов реализации Singleton содержит ленивую инициализацию. Это значит, что объект класса создаётся не в самом начале, а лишь когда будет получено первое обращение к нему. Добиться этого совсем не сложно:
Однако здесь начинаются проблемы с потоками, которые могут создавать несколько различных объектов. Происходит это примерно так:
Есть два способа решить эту проблему. Первый — пометить как synchronised не весь метод, а только блок, где создаётся объект:
Второй путь — использовать паттерн «Lazy Initialization Holder». Это решение основано на том, что вложенные классы не инициализируются до первого их использования (как раз то, что нам нужно):
Рефлексия
Мы запрещаем создавать несколько экземпляров класса, помечая конструктор приватным. Тем не менее, используя рефлексию, можно без особого труда изменить видимость конструктора с private на public прямо во время исполнения:
Конечно, если вы используете Singleton только в своём приложении, переживать не о чем. А вот если вы разрабатываете модуль, который затем будет использоваться в сторонних приложениях, то из-за этого могут возникнуть проблемы. Какие именно, зависит от того, что делает ваш «Одиночка» — это могут быть как и риски, связанные с безопасностью, так и просто непредсказуемое поведение модуля.
Заключение
Несмотря на то, что паттерн Singleton очень известный и популярный, у него есть множество серьёзных недостатков. Чем дальше, тем больше этих недостатков выявляется, и оригинальные паттерны из книги GOF «Design Patterns» часто сегодня считаются антипаттернами. Тем не менее, сама идея иметь лишь один объект на класс по-прежнему имеет смысл, но достаточно сложно реализовать ее правильно.
Security с характером, или еще несколько слов о паттерне Singleton
— Как, и он тоже singleton? А я думала, что он нормальный!
— Сколько раз тебе повторять, что слова singleton, mediator, decorator и даже prototype никакого отношения к ориентации кода не имеют?
(разговор вызова курильщика и вызова нормального программиста)
Всем привет, я Максим Кравец, CEO команды веб-инженеров Holyweb, адептов JavaScript. И сегодня хочу поговорить о паттернах проектирования.
Давайте взглянем на маленький кусочек кода:
Попробуйте найти разработчика, который прочитает этот фрагмент следующим образом: «Мы воспользуемся структурным паттерном decorator для обеспечения возможности внедрения методов нашего SomeClass в другие классы посредством Dependency Injection, в методе setData нашего класса применим порождающий паттерн Builder посредством new и в приватное поле currentData положим. »
Паттерны живут в нашем коде. Паттерны бывают порождающие, структурные, поведенческие. Мы ими пользуемся, даже не акцентируя на этом внимание, а строгие формулировки вспоминаем порой лишь при подготовке к собеседованию. Паттернов у программиста — что жен в гареме султана. И как те самые жены — они не очень любят, когда про них забывают и ими пренебрегают. И тогда наш код начинает делать нам нервы.
Так что сами — вспомним. А тем, кто еще не знает — расскажем. И начнем с паттерна, которому впору присвоить звание «нелюбимой жены султана». Можно даже встретить мнение, что его бездумное использование — безвкусица, дурной тон и вообще антипаттерн. Наш сегодняшний рассказ посвящен синглтону (паттерн Singleton).
Часть первая. Детективная. Ограбление, которого не было
Идея паттерна Singleton очень проста — на все приложение создается один-единственный инстанс класса, и при любом обращении возвращается именно этот инстанс. Техническая реализация паттерна также укладывается в одно предложение описания и одну строку кода:
описание:
Реализация должна скрыть конструктор класса и предоставить публичный статический метод, контролирующий жизненный цикл инстанса
код метода, контролирующего жизненный цикл:
Все это прекрасно, но зачем он нужен вообще? Ведь инстансы придумали не зря! Пусть у каждого компонента нашего приложения будет свой инстанс, которым он волен распоряжаться по своему усмотрению! Окей, давайте представим…
У вас есть семья (приложение), у семьи есть единый банковский счет (база данных) и несколько карт, привязанных к этому счету (инстансов класса, предоставляющего подключение к базе данных). На счету — 100 рублей (данные).
Вы и супруга (или супруг и вы) одновременно помещаете свои карточки в банкоматы и просите снять 100 рублей. Счет одномоментно получает два требования на списание, параллельно проводит две проверки на наличие средств, получает два подтверждения что такая сумма есть и параллельно списывает 100 рублей. Вы забираете из банкоматов каждый по сто рублей, забираете карточки…
Хорошо бы, но нет)) Даже весьма условный пример выше объясняет, почему в один момент времени к базе просто необходимо только одно подключение. Окей, но нас же (компонентов) несколько? Ждать очереди? Давайте еще немного напряжем воображение.
Теперь мы общаемся с банком (базой данных) по телефону. У нас только одна линия, и если кто-то еще попробует позвонить — услышит короткие гудки. Но нас же двое? Поступаем самым очевидным образом — включаем на своей стороне громкую связь! Как итог, линия связи одна (подключение). Телефон на нашей стороне один (инстанс), но пользоваться им могут все (различные компоненты нашего приложения. ), находящиеся в комнате с телефоном (. имеющие доступ к инстансу).
Мы только что создали синглтон в отдельно взятой ячейке общества, заодно определившись с тем, когда он нужен и как с ним работать. Ну что ж, давайте познакомимся с ним поближе.
Часть вторая. Романтическая. Продемонстрируем синглтону заинтересованность в нем
Singleton — это порождающий шаблон (паттерн), гарантирующий, что в однопоточном приложении будет только один экземпляр (инстанс) некоего класса, предоставляющий точку доступа к этому экземпляру.
Фактически, Singleton предоставляет глобальную точку доступа, но в отличие от простых глобальных переменных, которые также могут использоваться для решения этой задачи, Singleton скрывает от внешнего пользователя методы конструктора и тем самым гарантирует, что никакой сторонний код не сможет подменить данные.
Лучший друг Singletonа — это TypeScript, потому что он дает возможность обращаться через интерфейсы, существенно расширяя базовую функциональность.
Кроме того, несомненным плюсом Singleton является то, что он может быть создан «по запросу» — не при инициализации приложения, а при первом обращении. Однако тут следует быть осторожным и не забывать, что если объект нужен уже при инициализации, он может быть затребован раньше, чем будет создан.
Выбирая Singleton, стоит также помнить, что небрежное использование глобальных объектов может приводить к проблемам масштабируемости, контроля за многопоточностью, написания модульных тестов и в целом следования принципам TTD.
Так что же делать? Да то же, что и со всеми остальными паттернами! Использовать, помня о его несомненных плюсах, но не забывая о недостатках. Тем более, что проблемы с тестированием решаются с помощью методов внедрения зависимостей (DI), а при необходимости увеличить количество инстансов (отойти от паттерна Singleton) потребуется переписать всего один метод, отвечающий за доступ к инстансу.
Часть третья. Жизненная. Если б я был султан, я б имел трех жен?
Подведем итоги. Паттерн Singleton — мощный, а порой просто незаменимый инструмент в руках разработчика. Если использовать его по назначению. Микроскопом, как известно, тоже можно гвозди заколачивать, но вряд ли кто оценит. Так и с Singletonом. Ну а чтобы не запутались, вот вам сводная табличка:
Преимущества
Недостатки
Гарантирует наличие единственного инстанса класса.
Маскирует плохую архитектуру.
Реализует отложенную инициализацию.
Нарушает принцип единственной ответственности класса.
Предоставляет глобальную точку доступа.
Создает проблемы контроля многопоточности.
Отдельно стоит отметить, что Singleton может быть «включен» в состав других паттернов. Например, фасад нередко выступает в приложении в одиночку и может быть реализован как Singleton. Так же большая часть абстрактных классов при необходимости легко приводятся к виду Singletonа.
В общем, постарайтесь подружиться с этим полезным одиночкой, а мы тем временем подготовим рассказ о следующем паттерне. Впрочем, если вы уже считаете себя магистром Йодой в разработке или на пути к этому званию, тоже будет здорово познакомиться: пишите в Telegram @maximkravec.
Есть что дополнить? Оставляйте комментарии! Самые интересные мы добавим в статью, чтобы сделать ее лучше.
Синглтоны и общие экземпляры
Каждый раз при обсуждении программного обеспечения с другими разработчиками всплывает тема синглтонов, особенно в контексте развития WordPress’а. Я часто пытаюсь объяснить, почему их надо избегать, даже если они считаются стандартным шаблоном.
В данной статье я попытаюсь раскрыть тему того, почему синглтоны никогда не должны использоваться в коде и какие есть альтернативы для решения похожих проблем.
Что такое синглтон?
Синглтон — это шаблон проектирования в разработке программного обеспечения, описанный в книге Design Patterns: Elements of Reusable Object-Oriented Software (авторы — Банда четырёх), благодаря которой о шаблонах проектирования заговорили как об инструменте разработки ПО.
Идея в том, что вам может потребоваться, чтобы существовал лишь один экземпляр класса и чтобы вы предоставляли глобальную единую точку доступа к нему.
Это на самом деле достаточно просто объяснить и понять, и для многих людей синглтон — это лёгкий вход в мир шаблонов проектирования, что делает его самым популярным шаблоном.
Синглтон популярен, он был одним из первых шаблонов, описанных и стандартизированных в книге. Как же так получается, что некоторые разработчики считают его антишаблоном? Неужели он может быть настолько плохим?
Но синглтоны полезны и важны!
Я заметил, что многие люди путают два смежных понятия. Когда они говорят, что им нужен синглтон, им на самом деле нужно использовать один экземпляр объекта в разных операциях инстанцирования. В общем, когда вы создаёте инстанс, вы создаёте новый экземпляр этого класса. Но для некоторых объектов нужно всегда использовать один и тот же общий экземпляр (shared instance) объекта, вне зависимости от того, где он используется.
Но синглтон не является верным решением для этого.
Именно из-за второй функции люди выбирают синглтон, но эту задачу должен решать другой объект.
Нет ничего плохого в общем экземпляре. Но объект, который вы хотите для этого использовать, — не место для такого ограничения.
Ниже я покажу несколько альтернатив. Но сначала хочу рассказать, какие проблемы может вызвать синглтон.
Проблемы с синглтоном
Синглтон и SOLID
Прежде всего, и это может показаться скорее теоретической проблемой, синглтон нарушает многие принципы SOLID.
Шаблон синглтон нарушает четыре из пяти принципов SOLID. Он, возможно, хотел бы нарушить и пятый, если бы только мог иметь интерфейсы…
Легко сказать, что ваш код не работает только из-за каких-то теоретических принципов. И хотя, согласно моему собственному опыту, эти принципы — самое ценное и надёжное руководство, на которое можно опираться при разработке программного обеспечения, я понимаю, что просто слова «это факт» для многих звучат неубедительно. Мы должны проследить влияние синглтона на вашу повседневную практику.
Использования шаблона синглтон
Здесь перечислены недостатки, с которыми можно столкнуться, если иметь дело с синглтоном:
Вы не можете передавать/внедрять аргументы в конструктор. Поскольку при первом вызове синглтона реально исполняется только конструктор и вы не можете знать заранее, какой код первым обратится к синглтону, то во всём потребляющем коде нужно использовать один и тот же набор аргументов для передачи в конструктор, что во многих случаях почти невыполнимо, а вначале вообще бессмысленно. В результате синглтон делает бесполезным основной механизм инстанцирования в ООП-языках.
Вы не можете имитировать (mock away) синглтон при тестировании компонентов, использующих его. Это делает почти невозможным корректное модульное тестирование, потому что вы не добьётесь полной изоляции тестируемого кода. Проблема вызвана даже не самой логикой, которую вы хотите тестировать, а произвольным ограничением инстанцирования, в которое вы её оборачиваете.
Так как синглтон — глобально доступная конструкция, которая используется всей вашей кодовой базой, то идут прахом любые усилия по инкапсуляции, отчего появляются те же проблемы, что и в случае с глобальными переменными. То есть, как бы вы ни пытались изолировать синглтон в инкапсулированной части кода, любой другой внешний код может привести к побочным эффектам и багам в синглтоне. А без надлежащей инкапсуляции выхолащиваются сами принципы ООП.
Если у вас когда-либо был сайт или приложение, разросшееся настолько, что синглтону DatabaseConnection неожиданно понадобилось подключение ко второй, отличной от первой базе данных, значит, вы в беде. Придётся заново пересмотреть саму архитектуру и, возможно, полностью переписать значительную часть кода.
Все тесты, прямо или косвенно использующие синглтон, не могут корректно переключаться с одного на другой. Они всегда сохраняют состояние посредством синглтона, что может привести к неожиданному поведению там, где ваши тесты зависят от очерёдности запуска, или оставшееся состояние скроет от вас реальные баги.
Альтернативы синглтону
Я не хочу быть тем человеком, который во всём видит плохое, но не может предложить решение проблемы. Хотя я считаю, что вы должны оценивать всю архитектуру приложения, чтобы решить, как первым делом избежать использования синглтона, я предлагаю несколько самых распространённых способов в WordPress, когда синглтон можно легко заменить механизмом, который удовлетворяет всем требованиям и лишён большинства недостатков. Но прежде чем рассказать об этом, я хочу отметить, почему все мои предложения — только компромисс.
Существует «идеальная структура» для разработки приложений. Теоретически лучший вариант — единственный инстанцирующий вызов в загрузочном коде, создающий дерево зависимости приложения целиком, сверху донизу. Это будет работать так:
С помощью одного вызова выстроится сверху донизу весь стек приложения с внедрением зависимостей по мере необходимости. Цели такого подхода:
Однако, как бы хорошо это ни звучало, в WordPress’е так сделать невозможно, так как он не предоставляет централизованный контейнер или механизм внедрения, все плагины/темы загружаются изолированно.
Держите это в уме, пока мы будем обсуждать подходы. Идеальное решение, при котором весь стек WordPress’а инстанцируется через централизованный механизм внедрения, нам недоступно, поскольку оно требует поддержки WordPress Core. Всем описываемым далее подходам свойственны те или иные общие недостатки вроде сокрытия зависимостей посредством обращения к ним напрямую из логики вместо их внедрения.
Код синглтона
Пример кода с использованием синглтон-подхода, который мы будем сравнивать с другими:
Я не включил сюда все подробности реализации, с которыми часто загружаются синглтоны, потому что они неважны для теоретической дискуссии.
Фабричный метод
Учитывая название шаблона, вы можете подумать, что он больше похож на код Java, чем PHP-код, так что не стесняйтесь отклоняться от слишком строгого (и ленивого) соглашения об именовании и называйте фабрику более изобретательно.
Пример фабричного метода:
Приведённая версия фабрики избавлена почти от всех ранее описанных недостатков, за одним исключением.
Статичные заместители
Статичный заместитель (Static Proxy) — другой шаблон проектирования, на который можно поменять синглтон. Он подразумевает ещё более тесную связь, чем фабрика, но это хотя бы связь с абстракцией, а не с конкретной реализацией. Идея в том, что у вас есть статичное сопоставление (static mapping) интерфейса, и эти статичные вызовы прозрачно перенаправляются конкретной реализации. Таким образом, прямой связи с фактической реализацией нет, и статичный заместитель сам решает, как выбирать реализацию для использования.
Как видите, статичный заместитель создаёт очень короткий и чистый API. К недостаткам можно отнести то, что возникает тесная связь кода с сигнатурой класса. При использовании в правильном месте особых проблем это не вызывает, так как это связь с абстракцией, которую можно контролировать напрямую, а не с конкретной реализацией. Вы всё ещё можете заменить код одной базы данных на код другой, который вы считаете нужным, а реализация всё ещё является совершенно нормальным объектом, который может быть протестирован.
API WordPress Plugin
API WordPress Plugin может заменить синглтоны, когда те используются ради возможности обеспечения глобального доступа через плагины. Это самое чистое решение с учётом ограничений WordPress’а, с оговоркой, что вся инфраструктура и архитектура вашего кода привязывается к API WordPress Plugin. Не применяйте этот способ, если вы собираетесь заново использовать ваш код в разных фреймворках.
Один из основных компромиссов состоит в том, что ваша архитектура напрямую привязана к API WordPress Plugin. Если вы планируете когда-либо предоставлять функциональность плагина для Drupal-сайтов, то код придётся полностью переписать.
Другая возможная проблема — теперь вы зависите от тайминга WordPress-перехватчиков (hooks). Это может привести к багам, связанным с таймингом, их зачастую трудно воспроизвести и исправить.
Service Locator
Локатор служб — это одна из форм контейнера инверсии управления (Inversion of Control Container). Некоторые сайты описывают метод как антишаблон. С одной стороны, это правда, но с другой, как мы уже обсуждали выше, все предложенные здесь рекомендации можно считать лишь компромиссами.
Локатор служб — это контейнер, который предоставляет доступ к службам, реализованным в других местах. Контейнер по большей части — это коллекция экземпляров, сопоставленных с идентификаторами. Более сложные реализации локатора служб могут привносить такие возможности, как ленивое инстанцирование или генерирование заместителей.
Однако всё ещё нет ответа на вопрос, нужно ли пользоваться антишаблоном локатор служб вместо антишаблона синглтон… С локатором служб связана проблема: он «прячет» зависимости. Представим кодовую базу, которая использует правильное внедрение конструктора. В таком случае достаточно взглянуть на конструктор конкретного объекта, и можно сразу понять, от какого объекта он зависит. Если объект имеет доступ к ссылке на локатор служб, то вы можете обойти это явное разрешение зависимостей и извлечь ссылку (а следовательно, начать зависеть) на любой объект из реальной логики. Вот что имеют в виду, когда говорят, что локатор служб «прячет» зависимости.
Но, учитывая контекст WordPress, мы должны принять тот факт, что с самого начала нам недоступно идеальное решение. Нет технических возможностей реализовать правильное внедрение зависимостей по всей кодовой базе. Это значит, что нам в любом случае придётся искать компромисс. Локатор служб — не идеальное решение, однако этот шаблон хорошо укладывается в легаси-контекст и как минимум позволяет вам собрать все «компромиссы» в одном месте, а не раскидывать их по кодовой базе.
Внедрение зависимостей
Если вы работаете только в собственном плагине и вам не надо предоставлять доступ к своим объектам другим плагинам, то вам повезло: вы можете использовать настоящее внедрение зависимостей, чтобы избежать глобального доступа к зависимостям.
Выглядит немного сложнее, но имейте в виду, что мы должны были написать базовую версию всего плагина, чтобы продемонстрировать внедрение зависимостей.
Хотя у нас не может быть полного внедрения зависимостей во всём приложении, но мы, по крайней мере, можем получить его с ограничениями в плагине.
Это пример того, как всё соединить (wiring) вручную с помощью явного инстанцирования самих зависимостей. В более сложной кодовой базе вам захочется использовать автосоединяющееся внедрение зависимостей (Dependency Injector) (специализированный контейнер), которое принимает предварительную информацию о конфигурации и может рекурсивно инстанцировать целое дерево за один вызов.
Вот пример того, как можно сделать это соединение с помощью такого внедрения зависимостей (даны те же классы/интерфейсы, как и в предыдущем примере):
Комбинации
Для более сложных потребностей, которые разбросаны по нескольким пользовательским и сторонним плагинам, рассмотрите способы комбинирования рассмотренных подходов, которые не будут противоречить вашим условиям.
Например, в более сложных проектах я использовал следующий подход, чтобы приблизиться к идеальному решению, который описано выше:
На странице Bright Nucleus Architecture вы можете почитать об этом подходе и посмотреть записи.
Заключение
Несколько возможных подходов позволяют избавиться от синглтонов. Хотя ни один из них не идеален в контексте WordPress’а, они все без исключения предпочтительней синглтона.
Помните, что связанные с синглтонами проблемы заключаются не в том, что те расшарены, а в том, что синглтоны заставляют инстанцировать их самих.
Если вы знаете ситуации, когда синглтон — единственное подходящее решение, пишите в комментариях!