— Как, и он тоже singleton? А я думала, что он нормальный!
— Сколько раз тебе повторять, что слова singleton, mediator, decorator и даже prototype никакого отношения к ориентации кода не имеют?
(разговор вызова курильщика и вызова нормального программиста)
Всем привет, я Максим Кравец, CEO команды веб-инженеров Holyweb, адептов JavaScript. И сегодня хочу поговорить о паттернах проектирования.
Давайте взглянем на маленький кусочек кода:
@Injectable() export class SomeClass implements SomeInterface { private currentData: Data setData() { this.currentData = new Data() }
Попробуйте найти разработчика, который прочитает этот фрагмент следующим образом: «Мы воспользуемся структурным паттерном decorator для обеспечения возможности внедрения методов нашего SomeClass в другие классы посредством Dependency Injection, в методе setData нашего класса применим порождающий паттерн Builder посредством new и в приватное поле currentData положим…»
Паттерны живут в нашем коде. Паттерны бывают порождающие, структурные, поведенческие. Мы ими пользуемся, даже не акцентируя на этом внимание, а строгие формулировки вспоминаем порой лишь при подготовке к собеседованию. Паттернов у программиста — что жен в гареме султана. И как те самые жены — они не очень любят, когда про них забывают и ими пренебрегают. И тогда наш код начинает делать нам нервы.
Так что сами — вспомним. А тем, кто еще не знает — расскажем. И начнем с паттерна, которому впору присвоить звание «нелюбимой жены султана». Можно даже встретить мнение, что его бездумное использование — безвкусица, дурной тон и вообще антипаттерн. Наш сегодняшний рассказ посвящен синглтону (паттерн Singleton).
Часть первая. Детективная. Ограбление, которого не было
Идея паттерна Singleton очень проста — на все приложение создается один-единственный инстанс класса, и при любом обращении возвращается именно этот инстанс. Техническая реализация паттерна также укладывается в одно предложение описания и одну строку кода:
описание:
Реализация должна скрыть конструктор класса и предоставить публичный статический метод, контролирующий жизненный цикл инстанса
код метода, контролирующего жизненный цикл:
public static getInstance(): SingletonClassName { !instance ? instance = new SingletonClassName : instance }
Все это прекрасно, но зачем он нужен вообще? Ведь инстансы придумали не зря! Пусть у каждого компонента нашего приложения будет свой инстанс, которым он волен распоряжаться по своему усмотрению! Окей, давайте представим…
У вас есть семья (приложение), у семьи есть единый банковский счет (база данных) и несколько карт, привязанных к этому счету (инстансов класса, предоставляющего подключение к базе данных). На счету — 100 рублей (данные).
Вы и супруга (или супруг и вы) одновременно помещаете свои карточки в банкоматы и просите снять 100 рублей. Счет одномоментно получает два требования на списание, параллельно проводит две проверки на наличие средств, получает два подтверждения что такая сумма есть и параллельно списывает 100 рублей. Вы забираете из банкоматов каждый по сто рублей, забираете карточки…
Хорошо бы, но нет)) Даже весьма условный пример выше объясняет, почему в один момент времени к базе просто необходимо только одно подключение. Окей, но нас же (компонентов) несколько? Ждать очереди? Давайте еще немного напряжем воображение.
Теперь мы общаемся с банком (базой данных) по телефону. У нас только одна линия, и если кто-то еще попробует позвонить — услышит короткие гудки. Но нас же двое? Поступаем самым очевидным образом — включаем на своей стороне громкую связь! Как итог, линия связи одна (подключение). Телефон на нашей стороне один (инстанс), но пользоваться им могут все (различные компоненты нашего приложения…), находящиеся в комнате с телефоном (…имеющие доступ к инстансу).
Мы только что создали синглтон в отдельно взятой ячейке общества, заодно определившись с тем, когда он нужен и как с ним работать. Ну что ж, давайте познакомимся с ним поближе.
Часть вторая. Романтическая. Продемонстрируем синглтону заинтересованность в нем
Singleton — это порождающий шаблон (паттерн), гарантирующий, что в однопоточном приложении будет только один экземпляр (инстанс) некоего класса, предоставляющий точку доступа к этому экземпляру.
// создадим класс class DataBase { // объявим статическое поле для хранения объекта Singleton-а private static instance // сделаем приватным конструктор, чтобы никто не имел возможности // самостоятельно создавать инстансы нашего класса через new private constructor() { // здесь мы инициализируем подключение к базе данных …. // укажем, что инстанса изначально нет this.instance = null } // создадим доступную извне альтернативу конструктору // для того, чтобы обеспечить точку доступа к инстансу Singleton-а public static getInstance() { if ( this.instance === null ) { // если инстанса нет, создаем его this.instance = new DataBase() } // отдадим инстанс тому, кто запрашивал return this.instance } // для примера создадим метод query для формирования // запросов к базе данных public getQuery(payload) { // здесь реализуем логику запроса ... } } // создадим обращающийся класс class AppModule { // метод запроса к базе Data() { // объявим два подключения let foo = DataBase.getInstance() let bar = DataBase.getInstance() // foo содержит тот же объект, // что и bar foo.getQuery("SELECT ...") bar.getQuery("SELECT ...") } }
Фактически, Singleton предоставляет глобальную точку доступа, но в отличие от простых глобальных переменных, которые также могут использоваться для решения этой задачи, Singleton скрывает от внешнего пользователя методы конструктора и тем самым гарантирует, что никакой сторонний код не сможет подменить данные.
Лучший друг Singletonа — это TypeScript, потому что он дает возможность обращаться через интерфейсы, существенно расширяя базовую функциональность.
Кроме того, несомненным плюсом Singleton является то, что он может быть создан «по запросу» — не при инициализации приложения, а при первом обращении. Однако тут следует быть осторожным и не забывать, что если объект нужен уже при инициализации, он может быть затребован раньше, чем будет создан.
Выбирая Singleton, стоит также помнить, что небрежное использование глобальных объектов может приводить к проблемам масштабируемости, контроля за многопоточностью, написания модульных тестов и в целом следования принципам TTD.
Так что же делать? Да то же, что и со всеми остальными паттернами! Использовать, помня о его несомненных плюсах, но не забывая о недостатках. Тем более, что проблемы с тестированием решаются с помощью методов внедрения зависимостей (DI), а при необходимости увеличить количество инстансов (отойти от паттерна Singleton) потребуется переписать всего один метод, отвечающий за доступ к инстансу.
Часть третья. Жизненная. Если б я был султан, я б имел трех жен?
Подведем итоги. Паттерн Singleton — мощный, а порой просто незаменимый инструмент в руках разработчика. Если использовать его по назначению. Микроскопом, как известно, тоже можно гвозди заколачивать, но вряд ли кто оценит. Так и с Singletonом. Ну а чтобы не запутались, вот вам сводная табличка:
Преимущества |
Недостатки |
Гарантирует наличие единственного инстанса класса. |
Маскирует плохую архитектуру. |
Реализует отложенную инициализацию. |
Нарушает принцип единственной ответственности класса. |
Предоставляет глобальную точку доступа. |
Создает проблемы контроля многопоточности. |
Отдельно стоит отметить, что Singleton может быть «включен» в состав других паттернов. Например, фасад нередко выступает в приложении в одиночку и может быть реализован как Singleton. Так же большая часть абстрактных классов при необходимости легко приводятся к виду Singletonа.
В общем, постарайтесь подружиться с этим полезным одиночкой, а мы тем временем подготовим рассказ о следующем паттерне. Впрочем, если вы уже считаете себя магистром Йодой в разработке или на пути к этому званию, тоже будет здорово познакомиться: пишите в Telegram @maximkravec.
Есть что дополнить? Оставляйте комментарии! Самые интересные мы добавим в статью, чтобы сделать ее лучше.
ссылка на оригинал статьи https://habr.com/ru/post/552600/
Добавить комментарий