Security с характером, или еще несколько слов о паттерне Singleton

Kate

Administrator
Команда форума
Сегодня мы поговорим о паттернах проектирования.

Давайте взглянем на маленький кусочек кода:

@Injectable()
export class SomeClass implements SomeInterface {
private currentData: Data

setData() {
this.currentData = new Data()
}
Попробуйте найти разработчика, который прочитает этот фрагмент следующим образом: «Мы воспользуемся структурным паттерном decorator для обеспечения возможности внедрения методов нашего SomeClass в другие классы посредством Dependency Injection, в методе setData нашего класса применим порождающий паттерн Builder посредством new и в приватное поле currentData положим...»

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

Так что сами — вспомним. А тем, кто еще не знает — расскажем. И начнем с паттерна, которому впору присвоить звание «нелюбимой жены султана». Можно даже встретить мнение, что его бездумное использование — безвкусица, дурной тон и вообще антипаттерн. Наш сегодняшний рассказ посвящен синглтону (паттерн Singleton).

63a3341a140b28e312e0590ba9c5984b.png

Часть первая. Детективная. Ограбление, которого не было​

Идея паттерна 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а.

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

Источник статьи: https://habr.com/ru/post/552600/
 
Сверху