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

Ответ

Service Locator (Локатор служб) — это паттерн проектирования, предоставляющий централизованный реестр для получения зависимостей, скрывая их конкретные реализации от клиентского кода.

Реализация на Java:

public class ServiceLocator {
    private static final Map<Class<?>, Object> registry = new ConcurrentHashMap<>();

    public static <T> void register(Class<T> serviceInterface, T implementation) {
        registry.put(serviceInterface, implementation);
    }

    public static <T> T get(Class<T> serviceInterface) {
        T service = (T) registry.get(serviceInterface);
        if (service == null) {
            throw new IllegalStateException("Service not registered: " + serviceInterface.getName());
        }
        return service;
    }
}

// Регистрация и использование
ServiceLocator.register(DatabaseService.class, new PostgresDatabaseService());
DatabaseService db = ServiceLocator.get(DatabaseService.class);

Преимущества:

  • Снижение связанности — клиенты не зависят от конкретных классов реализаций
  • Гибкость — замена реализации в одном месте (например, PostgresDatabaseServiceMockDatabaseService)
  • Ленивая инициализация — сервисы создаются только при первом запросе

Недостатки:

  • Неявные зависимости — сложно отследить, какие сервисы использует класс
  • Проблемы тестирования — требуется настройка локатора перед каждым тестом
  • Нарушение инверсии зависимостей — код зависит от глобального состояния

Альтернатива — Dependency Injection (DI):

// Явное указание зависимостей через конструктор
public class UserService {
    private final DatabaseService db;

    public UserService(DatabaseService db) {  // Зависимость внедряется извне
        this.db = db;
    }
}

DI предпочтительнее, так как делает зависимости явными и упрощает модульное тестирование.

Ответ 18+ 🔞

А, слушай, паттерн Service Locator, да? Ну это такая штука, типа глобального прилавка, куда ты можешь прийти и сказать: «Дайте-ка мне, блядь, базу данных!», а тебе выдают, не спрашивая, откуда она взялась. Как в столовой совдеповской — повар в окошке, а что у него там на кухне творится, тебя не ебёт.

Вот смотри, как это на Java выглядит, тут всё чётко:

public class ServiceLocator {
    private static final Map<Class<?>, Object> registry = new ConcurrentHashMap<>();

    public static <T> void register(Class<T> serviceInterface, T implementation) {
        registry.put(serviceInterface, implementation);
    }

    public static <T> T get(Class<T> serviceInterface) {
        T service = (T) registry.get(serviceInterface);
        if (service == null) {
            throw new IllegalStateException("Service not registered: " + serviceInterface.getName());
        }
        return service;
    }
}

// Регистрация и использование
ServiceLocator.register(DatabaseService.class, new PostgresDatabaseService());
DatabaseService db = ServiceLocator.get(DatabaseService.class);

Вроде удобно, да? Засунул в статическую мапу свою реализацию и потом отовсюду таскаешь. Преимущества, блядь, вроде как есть:

  • Связанность снижается — твоим классам похуй, кто там на кухне готовит, PostgresDatabaseService или ХуяксDatabaseService.
  • Гибкость — захотел поменять реализацию, ебнул одну строчку в локаторе, и всё, пиздец, везде подменилось. Красота.
  • Ленивая инициализация — сервис создастся только когда его впервые спросят, а не при старте приложения, что иногда экономит ресурсы.

Но вот недостатки, сука, такие, что волосы дыбом:

  • Зависимости неявные — смотришь ты на класс, а нихрена не понятно, что он там из локатора вытаскивает. Прям как шпион какой-то, блядь. Искать эти вызовы — тот ещё пиздец.
  • Тестирование превращается в ад — перед каждым тестом надо этот ёбаный локатор настраивать, подсовывать ему моки, а потом не забыть почистить. Один раз забудешь — и тесты друг другу ебут мозги через общее состояние. Терпения ноль, ебать.
  • Инверсия зависимостей нарушена в хлам. Код начинает зависеть от глобального статического состояния, а это, простите, пизда. Как та самая мартышлюшка, которая везде суёт свои пальцы.

Поэтому умные дядьки придумали альтернативу — Dependency Injection (DI). Это когда зависимости тебе явно в конструктор или в поле тычут, а не ты их из тёмного угла выковыриваешь.

// Явное указание зависимостей через конструктор
public class UserService {
    private final DatabaseService db;

    public UserService(DatabaseService db) {  // Зависимость внедряется извне
        this.db = db;
    }
}

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

Итог: Service Locator — это как быстрая поножовщина в подворотне. Сработает, но последствия могут быть ебаными. DI — это цивилизованная дуэль по правилам. Выбор, в общем-то, очевиден, если ты не конченый максималист, как тот Герасим из рассказа, который сразу всё мочить полез.