Что такое паттерн Service Locator?

Ответ

Service Locator (Локатор служб) — это паттерн проектирования, который предоставляет глобальный реестр для поиска и получения зависимостей (сервисов), скрывая детали их создания. Это альтернатива Dependency Injection (DI), где зависимости не "внедряются", а "запрашиваются" клиентом.

Базовая реализация на Swift:

class ServiceLocator {
    static let shared = ServiceLocator()
    private var services: [String: Any] = [:]

    // Регистрация сервиса по его типу
    func register<T>(_ service: T) {
        let key = String(describing: T.self)
        services[key] = service
    }

    // Разрешение (получение) сервиса по типу
    func resolve<T>() -> T? {
        let key = String(describing: T.self)
        return services[key] as? T
    }
}

// 1. Регистрация сервисов (обычно при запуске приложения)
ServiceLocator.shared.register(NetworkService())
ServiceLocator.shared.register(DatabaseService())

// 2. Использование в коде
class ViewModel {
    // Зависимость неявно получена через локатор
    private let networkService: NetworkService? = ServiceLocator.shared.resolve()
}
Сравнение с Dependency Injection (DI): Аспект Service Locator Dependency Injection (через конструктор)
Видимость зависимостей Скрыты (класс сам их ищет) Явные (передаются как параметры)
Тестируемость Сложнее, требует настройки локатора в тестах Легче, зависимости можно передать как mock/stub
Связность кода Выше (класс знает о локаторе) Ниже (класс знает только о своих интерфейсах)

Недостатки (почему часто считается антипаттерном):

  • Скрытые зависимости: Усложняет понимание того, от чего зависит класс.
  • Усложняет тестирование: Требует конфигурации глобального состояния (shared) для каждого теста.
  • Зависимость от глобального состояния: Противоречит принципам модульности и может привести к непредсказуемому поведению.

Ответ 18+ 🔞

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

Service Locator (Локатор служб) — это, грубо говоря, такой глобальный шкафчик, куда ты складываешь все свои сервисы, а потом, когда надо, суёшь туда руку и пытаешься нащупать нужный. Вместо того чтобы тебе зависимости аккуратно в руки давали (как в Dependency Injection), ты сам их из этого шкафа выковыриваешь. Удобно? Иногда. Опасно? Ебать как.

Вот смотри, как это выглядит в коде, если делать по-простому:

class ServiceLocator {
    static let shared = ServiceLocator() // Один на всех, глобальный как совесть у политика
    private var services: [String: Any] = [:] // А тут всё лежит в одной куче, как в бардачке

    // Кидаешь сервис в эту кучу
    func register<T>(_ service: T) {
        let key = String(describing: T.self)
        services[key] = service
    }

    // Пытаешься найти в этой куче то, что тебе надо
    func resolve<T>() -> T? {
        let key = String(describing: T.self)
        return services[key] as? T // А если не найдешь — получишь nil, и пиши пропало
    }
}

// 1. Засовываем всё в шкаф при старте (обычно в AppDelegate, где и так бардак)
ServiceLocator.shared.register(NetworkService())
ServiceLocator.shared.register(DatabaseService())

// 2. А потом где-нибудь в коде хватаем, что попало
class ViewModel {
    // А откуда это взялось? Хрен его знает, спроси у шкафа!
    private let networkService: NetworkService? = ServiceLocator.shared.resolve()
}

А теперь давай сравним с нормальным Dependency Injection, чтобы понять, где собака порылась:

Что сравниваем Service Locator (наш шкаф) Dependency Injection (через конструктор)
Видно ли зависимости? Скрыты, как пасхалка. Класс сам шарится по глобальному шкафу. Да чётко, как в декларации. Все зависимости перечислены при создании.
Как тестировать? О, ёпта, веселье! Надо перед каждым тестом настраивать этот глобальный шкаф, а потом не забыть его почистить. Легко и просто. Подсовываешь заглушки (mock/stub) прямо в конструктор и спишь спокойно.
Связанность кода Высокая, блядь. Класс привязан к этому локатору, как алкаш к ларьку. Низкая. Классу похуй, откуда пришли зависимости, главное — чтобы они были нужного типа.

Почему его часто ругают и называют антипаттерном?

  • Зависимости-невидимки: Открываешь класс, а нихуя не понятно, что ему нужно для работы. Всё спрятано в вызовах к локатору. Через полгода сам будешь ебаться, разбираясь.
  • Тесты превращаются в ад: Представь, у тебя 100 тестов, и каждый лезет в один глобальный shared локатор. Один тест его испортил — все остальные посыпались. Волнение ебать, терпения ноль ебать.
  • Глобальное состояние — зло: Это как ходить по охуенно тонкому льду. Одна часть приложения положила в локатор одну версию сервиса, другая — другую, и понеслась жопа. Отлаживать такое — чих-пых тебя в сраку.

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