Избавляемся от Singleton-зависимости в Swift

И

«Я в курсе, что Singleton — это антипаттерн, но…». Разработчики часто говорят это при обсуждении кода. За годы обсуждений программисты достигли согласия в том, что паттерн «одиночка» — это плохо. Тем не менее, Apple и многие разработчики на языке программирования Swift продолжают использовать Singleton, как в своих приложениях, так и в фреймворках. Давайте же рассмотрим несколько полезных для избежания «одиночек» методов!

Почему анти-паттерн Singleton так популярен?

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

Ответ, вероятно, следует разбить на две части.

Во-первых, популярность шаблона «Одиночка», вероятно, обоснована тем, что Apple сами не пренебрегают его использованием. Многие сторонние разработчики, как и я сам, часто обращаются к коду Apple в поисках лучших примеров; почти всегда большинство разработчиков слепо повторяют код за Apple, как эталонный и неоспоримый.

Во-вторых — удобство. Singleton часто может играть роль «тега» для быстрого доступа к основным значениям или объектам, так как, в основном, они доступны из любой точки приложения. Просто посмотрите на пример. Есть класс AccountViewController, который отображает вошедшего по имени (nameLabel) пользователя, а так же выходит из аккаунта (signOutHandle) по нажатию на кнопку:

В этом коде AccountManager (это как раз наш Singleton) отвечает за вход и выход пользователя из аккаунта на все приложение. Это и правда очень удобно, и это очень распространенная реализация подобной задачи. Так что же в этом шаблоне тогда плохо?

Почему использовать Singleton плохо?

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

Так какие же у Одиночек проблемы, почему их стоит избегать? Есть три основные причины. Давайте же рассмотрим их:

  1. Они глобальны, изменяемы, общедоступны. Состояние Одиночки автоматически распространяется на все приложение, и ошибки очень часто происходят именно тогда, когда их, одиночек, состояние изменяется.
  2. Отношения между одиночками и кодом , который зависит от них, как правило , не очень хорошо определены. Поскольку к одиночкам очень легко получить доступ — широкое их использование часто приводит к созданию трудноподдерживаемого «спагетти-кода», который не имеет четких разделений между объектами.
  3. Управление жизненным циклом может быть затруднено. Так как одиночки «живут» в течении всего периода жизни приложения, управлять ими бывает очень трудно. Так же код, который связан с одиночками, очень трудно тестировать, так как вы не можете начать «с чистого листа» в каждом тесте.

В нашем раннем примере AccountViewController мы уже видим признаки этих 3х проблем. Очень неочевидно, что зависит от AccountManager и есть ли у него доступ к currentUser. Так же мы не знаем, будет ли хоть что то кроме nil внутри currentUser, так как он опционален. Похоже, ошибка только и ждет момента, что бы случится.

Внедрение зависимости

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

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

Сервисы

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

Модификация

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

Вместо рефакторинга одиночек и создания новых классов, мы просто можем определить наши функции в качестве протоколов:

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

Этот же метод может быть использован для модификации других основных объектов в приложении, которые могли бы быть использованы как Singleton. К примеру, можно использовать AppDelegate для NavigationService:

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

Паттерн Singleton, вывод

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

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

Поддержите ресурс blog.justDev:

Сведения об авторе

Игорь Малеваный

2 комментария

  • Это все здорово, но несколько оторвано от реальности.

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

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

Instagram

Поддержите ресурс blog.justDev:

Свежие записи

Рубрики