Что такое паттерн Сервис Локатор (Service Locator)?

Ответ

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

Основная идея: Скрыть сложность создания и нахождения сервисов, уменьшая связанность между классами.

Пример простой реализации на Java:

import java.util.HashMap;
import java.util.Map;

// Класс-локатор сервисов
public class ServiceLocator {
    private static final Map<Class<?>, Object> services = new HashMap<>();

    // Регистрация сервиса в локаторе
    public static <T> void registerService(Class<T> interfaceClass, T implementation) {
        services.put(interfaceClass, implementation);
    }

    // Получение сервиса из локатора
    public static <T> T getService(Class<T> interfaceClass) {
        return (T) services.get(interfaceClass);
    }
}

// Интерфейс и его реализация
interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Log to file: " + message);
    }
}

// Использование в коде приложения
public class MyApp {
    public void doWork() {
        Logger logger = ServiceLocator.getService(Logger.class);
        logger.log("Application started");
    }
}

// Конфигурация (обычно при старте приложения)
class AppConfig {
    public static void configure() {
        ServiceLocator.registerService(Logger.class, new FileLogger());
    }
}

Влияние на процесс тестирования (QA):

  • Плюс для модульного тестирования: В тестах можно легко подменить реальный сервис на mock- или stub-объект, зарегистрировав его в локаторе перед запуском теста.

    @Test
    public void testDoWork() {
        // Подменяем реальный логгер на заглушку
        Logger mockLogger = Mockito.mock(Logger.class);
        ServiceLocator.registerService(Logger.class, mockLogger);
    
        MyApp app = new MyApp();
        app.doWork();
    
        // Проверяем, что метод log был вызван
        Mockito.verify(mockLogger).log("Application started");
    }
  • Минус — скрытые зависимости: Паттерн делает зависимости неявными. Класс MyApp не показывает, что ему нужен Logger, что усложняет анализ кода и может привести к ошибкам времени выполнения, если сервис не был зарегистрирован. Это критичный момент для интеграционного тестирования — нужно проверять корректность конфигурации локатора.
  • Альтернатива — Внедрение зависимостей (DI): В современных фреймворках (Spring, Guice) чаще используется DI, где зависимости передаются явно (через конструктор или сеттер), что делает код более прозрачным и удобным для тестирования.

Ответ 18+ 🔞

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

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

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

import java.util.HashMap;
import java.util.Map;

// Сам локатор, типа реестра
public class ServiceLocator {
    private static final Map<Class<?>, Object> services = new HashMap<>();

    // Запихнуть сервис в реестр
    public static <T> void registerService(Class<T> interfaceClass, T implementation) {
        services.put(interfaceClass, implementation);
    }

    // Выковырять сервис из реестра
    public static <T> T getService(Class<T> interfaceClass) {
        return (T) services.get(interfaceClass);
    }
}

// Интерфейс и его реализация
interface Logger {
    void log(String message);
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Log to file: " + message);
    }
}

// Как этим в коде пользуются
public class MyApp {
    public void doWork() {
        Logger logger = ServiceLocator.getService(Logger.class);
        logger.log("Application started");
    }
}

// Настройка всей этой бадяги (обычно при старте)
class AppConfig {
    public static void configure() {
        ServiceLocator.registerService(Logger.class, new FileLogger());
    }
}

А теперь, что это для тестировщика значит, ёпта:

  • Плюс, конечно, есть: Для юнит-тестов — просто красота. Можешь перед тестом взять и подсунуть в локатор какую-нибудь заглушку (mock) вместо настоящего сервиса. Волнение ебать — никакое, всё под контролем.

    @Test
    public void testDoWork() {
        // Меняем реальный логгер на муляж
        Logger mockLogger = Mockito.mock(Logger.class);
        ServiceLocator.registerService(Logger.class, mockLogger);
    
        MyApp app = new MyApp();
        app.doWork();
    
        // Проверяем, что лог-то вызвался
        Mockito.verify(mockLogger).log("Application started");
    }
  • А вот минус — он жирный: Зависимости становятся скрытыми, блядь. Смотришь на класс MyApp — и нихуя не видно, что ему логгер нужен. Он просто из воздуха его достаёт. Это ж доверия ебать ноль к такому коду! Можно запросто получить ошибку в рантайме, если забыл сервис в локатор зарегить. На интеграционных тестах за этим надо следить, как ястреб, чтобы конфигурация была полная.
  • Поэтому сейчас мода пошла другая — Внедрение зависимостей (DI): В этих ваших Spring'ах и Guice'ах зависимости прямо в конструктор пихают или в поля. Всё на виду, всё прозрачно. Код читать легче, и тестировать его — одно удовольствие, а не ёперный театр со скрытыми сюрпризами.