178
behavioral loosely coupled

Service Locator

Reference Wikipedia ↗
Service Locator — class diagram
Plate 178 class diagram

The Service Locator pattern provides a centralized registry for obtaining services. Instead of a component directly creating or finding its dependencies, it asks the Service Locator for them. This enhances decoupling by hiding the implementation details of the services from the clients that use them. The Locator is responsible for knowing where and how to get or create the services.

This pattern is useful in scenarios where you need to abstract the dependency resolution process, facilitate testing with mock services, or dynamically configure which services are available. It’s often employed in applications with a complex dependency graph and allows for easier maintenance and extension. However, overuse can lead to hidden dependencies and make understanding the flow of control more challenging.

Usage

The Service Locator pattern is frequently used in:

  • Large applications with many dependencies: Simplifying module interactions and maintaining a clean architecture.
  • Testing environments: Replacing real services with mock implementations to isolate units of code.
  • Plug-in architectures: Dynamically registering and retrieving service providers at runtime.
  • Frameworks: Providing a standardized way to access shared resources.

Examples

  1. Spring Framework (Java): Spring’s ApplicationContext acts as a Service Locator. Components declare their dependencies as constructor parameters or setter methods, and Spring automatically resolves and injects those dependencies from its registry. Developers can configure this registry via XML, annotations or Java code.

  2. Angular Dependency Injection (TypeScript): While Angular’s DI is more sophisticated than a simple Service Locator, it shares core concepts. Components register their dependencies with the Injector, and Angular resolves and provides instances of those dependencies when the component needs them. @Injectable() decorator marks a class as a provider and makes it available to the Injector.

  3. Unity (C#): A dependency injection container for .NET that can function as a Service Locator. You register types and their implementations with the Unity container, and then resolve instances using the Resolve() method. This allows components to request dependencies without knowing their concrete types or how they are created.

Specimens

15 implementations
Specimen 178.01 Dart View specimen ↗

The Service Locator pattern provides a centralized way to access services within an application. Instead of a component directly creating or finding its dependencies, it asks a locator to provide them. This promotes loose coupling and makes testing easier because dependencies can be swapped out. In Dart, this is often implemented as a simple class with a map to store services, accessed via string keys. The code utilizes a singleton pattern for the locator itself, a common practice in Dart for global points of access.

// service_locator.dart

import 'package:flutter/foundation.dart';

abstract class Service {}

class AnalyticsService implements Service {
  void trackEvent(String eventName) {
    print('Tracking event: $eventName');
  }
}

class ConfigurationService implements Service {
  String get apiUrl => 'https://example.com/api';
}


class ServiceLocator {
  static final ServiceLocator _instance = ServiceLocator._internal();

  factory ServiceLocator() {
    return _instance;
  }

  ServiceLocator._internal();

  final Map<String, Service> _services = {};

  void register<T extends Service>(String key, T service) {
    _services[key] = service;
  }

  T get<T extends Service>(String key) {
    final service = _services[key];
    if (service == null) {
      throw ArgumentError('Service not found with key: $key');
    }
    return service as T;
  }

  void unregister(String key) {
    _services.remove(key);
  }
}

// Example Usage (in main.dart or another file)
void main() {
  final locator = ServiceLocator();
  locator.register<AnalyticsService>('analytics', AnalyticsService());
  locator.register<ConfigurationService>('config', ConfigurationService());

  final analytics = locator.get<AnalyticsService>('analytics');
  analytics.trackEvent('App Started');

  final config = locator.get<ConfigurationService>('config');
  print('API URL: ${config.apiUrl}');
}