Service Locator
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
-
Spring Framework (Java): Spring’s
ApplicationContextacts 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. -
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. -
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 implementationsThe 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}');
}
The Service Locator pattern provides a central point of access to various services within an application. Instead of hardcoding dependencies or using explicit constructor injection, components request services from a central “locator” object. This enhances decoupling and makes it easier to swap implementations.
This Scala implementation uses a companion object to act as the service locator. Services holds a Map of service names to their corresponding instances. getService retrieves an instance by name, and registerService allows adding services. It’s idiomatic Scala due to the use of immutable maps, companion objects for organizing related functionality, and the flexibility of Scala’s type system. Pattern matching in getService provides a safe way to handle missing services.
object Services {
private val services: Map[String, Any] = Map.empty
def registerService[T](name: String, service: T): Unit = {
//In a real scenario would want to avoid direct mutable state, potentially using a Ref/Var
//given the immutability focus of Scala.
println(s"Registering service: $name")
}
def getService[T](name: String): Option[T] = {
println(s"Requesting service: $name")
// This could be replaced with a lookup in a more robust storage system.
// In this example, we return None as services aren't actually stored.
None
}
}
class MyComponent {
def doSomething(): Unit = {
val logger = Services.getService[Logger].getOrElse(new DefaultLogger)
logger.log("MyComponent doing something")
}
}
trait Logger {
def log(message: String): Unit
}
class DefaultLogger extends Logger {
override def log(message: String): Unit = println(s"LOG: $message")
}
object Main extends App {
val component = new MyComponent()
component.doSomething()
}
The Service Locator is a design pattern that centralizes the retrieval of dependencies (services) for an application. Instead of a component directly creating or locating its dependencies, it asks the Service Locator for them. This promotes loose coupling and makes it easier to substitute different implementations of services without modifying the dependent components.
The PHP code below demonstrates a simple Service Locator. A ServiceLocator class manages a registry of services, retrieved by keys. Components then request services using the $locator->getService('serviceName') method. This is an idiomatic PHP approach using a static registry (though dependency injection containers are more common for larger applications), and leveraging PHP’s flexible function/method calling mechanisms. It avoids tight coupling by centralizing dependency resolution.
<?php
class ServiceLocator
{
/** @var array<string, callable> */
private static $services = [];
public static function registerService(string $name, callable $callback): void
{
self::$services[$name] = $callback;
}
public static function getService(string $name): mixed
{
if (!isset(self::$services[$name])) {
throw new \InvalidArgumentException("Service '$name' not registered.");
}
return self::$services[$name]();
}
}
// Define some services
ServiceLocator::registerService(
'database',
function () {
return new DatabaseConnection('localhost', 'root', 'password', 'mydb');
}
);
ServiceLocator::registerService(
'logger',
function () {
return new Logger('app.log');
}
);
class DatabaseConnection {
public function __construct(string $host, string $user, string $pass, string $db) {
// Simulate database connection
echo "Connecting to database: $host/$db with user $user\n";
}
public function query(string $sql): void {
echo "Executing query: $sql\n";
}
}
class Logger {
private string $filename;
public function __construct(string $filename) {
$this->filename = $filename;
echo "Logger initialized, logging to $filename\n";
}
public function log(string $message): void {
echo "Logging: $message\n";
}
}
class MyComponent
{
private DatabaseConnection $db;
private Logger $logger;
public function __construct()
{
$this->db = ServiceLocator::getService('database');
$this->logger = ServiceLocator::getService('logger');
}
public function doSomething(): void
{
$this->db->query('SELECT * FROM users');
$this->logger->log('Component did something.');
}
}
// Usage
$component = new MyComponent();
$component->doSomething();
?>
The Service Locator pattern provides a central point of access to various services within an application. Instead of a class directly creating or finding its dependencies, it asks the locator for them. This promotes loose coupling and makes it easier to swap implementations or manage dependencies.
The Ruby implementation uses a simple hash to store services, keyed by their symbolic names. The ServiceLocator class provides methods to register and retrieve services. It’s idiomatic Ruby because it leverages hashes – a core data structure – for dependency management and utilizes simple method calls for interaction. No complex class hierarchies are established as would be more common in statically typed languages. The focus is on conciseness and flexibility, in line with Ruby’s principles.
# service_locator.rb
class ServiceLocator
@@services = {}
def self.register(name, service)
@@services[name] = service
end
def self.resolve(name)
raise ArgumentError, "Service not registered: #{name}" unless @@services.key?(name)
@@services[name]
end
end
# Example Services
class DatabaseConnection
def connect
puts "Connecting to the database..."
end
end
class LoggerService
def log(message)
puts "[LOG] #{message}"
end
end
# Register the services
ServiceLocator.register(:db, DatabaseConnection.new)
ServiceLocator.register(:logger, LoggerService.new)
# Resolve and use the services
db_connection = ServiceLocator.resolve(:db)
db_connection.connect
logger = ServiceLocator.resolve(:logger)
logger.log("Application started.")
The Service Locator pattern provides a centralized way to access services within an application. Instead of a component directly creating or locating its dependencies, it asks a “locator” for them. This promotes loose coupling, makes testing easier by allowing dependency injection through the locator, and simplifies management of complex dependencies. The Swift implementation uses a static shared instance to act as the central registry. Services are registered using a dictionary keyed by a designated type. The code demonstrates registration of a Logger service and its subsequent retrieval in a ViewController. This approach aligns well with Swift’s reliance on dependency injection and ease of use of dictionaries.
// ServiceLocator.swift
protocol Service { }
final class ServiceLocator {
static let shared = ServiceLocator()
private init() {}
private var services: [Type: Any] = [:]
func register<T: Service>(service: T) {
services[T.self] = service
}
func resolve<T: Service>() -> T? {
return services[T.self] as? T
}
}
// LoggerService.swift
protocol Logger: Service {
func log(message: String)
}
class DefaultLogger: Logger {
func log(message: String) {
print("LOG: \(message)")
}
}
// ViewController.swift
class MyViewController: UIViewController {
let logger: Logger? = ServiceLocator.shared.resolve()
override func viewDidLoad() {
super.viewDidLoad()
logger?.log(message: "View controller loaded.")
}
}
// Usage (e.g., in your app delegate or main setup)
func setupServices() {
ServiceLocator.shared.register(service: DefaultLogger())
}
setupServices()
// Example of using the view controller:
let viewController = MyViewController()
viewController.view.backgroundColor = .white
viewController.title = "Service Locator Demo"
The Service Locator pattern provides a centralized way to obtain dependencies (services) without tightly coupling a client to concrete implementations. Instead of injecting dependencies directly, clients ask a central “locator” for the service they need, identified by a key or name. This allows for configuration changes and service swapping without modifying the client code.
The Kotlin implementation uses a simple ServiceLocator object as the central registry. Services are stored in a mutableMapOf using a String key for identification. getService() retrieves a service, and registerService() adds one. Kotlin’s object declarations and concise syntax fit well with this pattern, making it easy to create and use the locator. This approach favors simplicity and doesn’t require complex dependency injection frameworks for basic scenarios.
// ServiceLocator.kt
object ServiceLocator {
private val services = mutableMapOf<String, Any>()
fun <T> getService(key: String): T? {
return services[key] as? T
}
fun <T> registerService(key: String, service: T) {
services[key] = service
}
}
// Define some services
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) {
println("Console: $message")
}
}
class FileLogger(private val filename: String) : Logger {
override fun log(message: String) {
println("File: $message (logging to $filename)")
}
}
// Client code
fun main() {
// Register services
ServiceLocator.registerService("consoleLogger", ConsoleLogger())
ServiceLocator.registerService("fileLogger", FileLogger("app.log"))
// Get and use services
val consoleLogger = ServiceLocator.getService<Logger>("consoleLogger")
consoleLogger?.log("This is a console log message.")
val fileLogger = ServiceLocator.getService<Logger>("fileLogger")
fileLogger?.log("This is a file log message.")
//Demonstrate handling missing service
val missingLogger = ServiceLocator.getService<Logger>("unknownLogger")
println("Missing logger: $missingLogger") // Prints "Missing logger: null"
}
The Service Locator pattern provides a centralized way to access various services within an application. Instead of directly depending on concrete implementations, components request services from a central registry (the locator). This promotes loose coupling and makes it easier to swap out implementations.
The Rust implementation uses a ServiceLocator struct holding a HashMap to store services identified by keys (strings). The provide method registers a service, and the get method retrieves it, potentially panicking if not found. Using a HashMap is a common, efficient way to manage these lookups in Rust. The pattern leverages Rust’s ownership and borrowing to ensure services aren’t accidentally dropped while being accessed. Traits are key here, enabling flexible service registration and retrieval based on types rather than concrete structures.
use std::collections::HashMap;
// Define a trait for our services
trait Service {
fn execute(&self);
}
// Concrete service implementations
struct LoggingService;
impl Service for LoggingService {
fn execute(&self) {
println!("Logging to file...");
}
}
struct AnalyticsService;
impl Service for AnalyticsService {
fn execute(&self) {
println!("Sending analytics data...");
}
}
// The Service Locator
struct ServiceLocator {
services: HashMap<String, Box<dyn Service>>,
}
impl ServiceLocator {
fn new() -> Self {
ServiceLocator {
services: HashMap::new(),
}
}
fn provide<T: Service>(&mut self, name: &str, service: T) {
self.services.insert(name.to_string(), Box::new(service));
}
fn get<T: Service>(&self, name: &str) -> &T {
self.services
.get(name)
.expect(&format!("Service with name '{}' not found", name))
.as_ref()
}
}
fn main() {
let mut locator = ServiceLocator::new();
locator.provide("logger", LoggingService);
locator.provide("analytics", AnalyticsService);
let logger = locator.get::<LoggingService>("logger");
logger.execute();
let analytics = locator.get::<AnalyticsService>("analytics");
analytics.execute();
}
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 requests them from a central “locator.” This promotes loose coupling and simplifies dependency management, particularly in complex systems.
The Go implementation uses an interface for the locator and a map to store services. A Register function adds services keyed by a string, and a Get function retrieves them. Error handling is included for cases where a requested service isn’t found. This approach leverages Go’s interfaces and maps, fitting its style of explicit dependency injection through interfaces and avoiding tight coupling through concrete types. The use of a map is a natural way to implement a lookup table in Go.
package main
import (
"fmt"
"errors"
)
// Service is the interface that all services must implement.
type Service interface {
Execute() string
}
// ConcreteServiceA is a concrete implementation of the Service interface.
type ConcreteServiceA struct {
Name string
}
func (s *ConcreteServiceA) Execute() string {
return fmt.Sprintf("Service A executed by %s", s.Name)
}
// ConcreteServiceB is another concrete implementation.
type ConcreteServiceB struct {
Message string
}
func (s *ConcreteServiceB) Execute() string {
return fmt.Sprintf("Service B says: %s", s.Message)
}
// Locator is the interface for the service locator.
type Locator interface {
Register(name string, service Service)
Get(name string) (Service, error)
}
// serviceLocator implements the Locator interface.
type serviceLocator struct {
services map[string]Service
}
// NewServiceLocator creates a new service locator.
func NewServiceLocator() Locator {
return &serviceLocator{
services: make(map[string]Service),
}
}
// Register registers a service with the locator.
func (sl *serviceLocator) Register(name string, service Service) {
sl.services[name] = service
}
// Get retrieves a service from the locator.
func (sl *serviceLocator) Get(name string) (Service, error) {
service, ok := sl.services[name]
if !ok {
return nil, errors.New("service not found")
}
return service, nil
}
func main() {
locator := NewServiceLocator()
locator.Register("A", &ConcreteServiceA{Name: "Alice"})
locator.Register("B", &ConcreteServiceB{Message: "Hello, world!"})
serviceA, err := locator.Get("A")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(serviceA.Execute())
}
serviceB, err := locator.Get("B")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(serviceB.Execute())
}
_, err = locator.Get("C")
if err != nil {
fmt.Println(err)
}
}
The Service Locator pattern provides a central point of access to dependencies, decoupling the client code from concrete implementations. Instead of directly creating or looking up dependencies, clients request them from a “locator” which handles the instantiation and management. This enhances modularity and testability, as dependencies can be easily swapped.
The following C implementation uses a simple global structure acting as the locator. Functions get_service() retrieve services based on a string key. A service registration function register_service() allows dependencies to be added. This approach, while basic, aligns with C’s typical use of global state for configuration and dependency management, making it immediately understandable for C developers. It leans heavily on function pointers for flexibility.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Define a service interface (function pointer)
typedef int (*MyServiceFunc)(int);
// Structure to hold service registration information
typedef struct {
char* name;
MyServiceFunc function;
} ServiceRegistration;
// Global Service Locator
static ServiceRegistration services[10]; // Limited to 10 services for simplicity
static int service_count = 0;
// Function to register a service
void register_service(const char* name, MyServiceFunc func) {
if (service_count < 10) {
services[service_count].name = strdup(name);
services[service_count].function = func;
service_count++;
} else {
fprintf(stderr, "Service Locator is full!\n");
}
}
// Function to retrieve a service
MyServiceFunc get_service(const char* name) {
for (int i = 0; i < service_count; ++i) {
if (strcmp(services[i].name, name) == 0) {
return services[i].function;
}
}
return NULL; // Service not found
}
// Example Services
int service_one(int input) {
return input * 2;
}
int service_two(int input) {
return input + 5;
}
int main() {
// Register the services
register_service("one", service_one);
register_service("two", service_two);
// Retrieve the services
MyServiceFunc svc1 = get_service("one");
MyServiceFunc svc2 = get_service("two");
if (svc1 && svc2) {
printf("Service one(10) = %d\n", svc1(10));
printf("Service two(10) = %d\n", svc2(10));
} else {
printf("Service not found!\n");
}
//cleanup (important when using strdup)
for (int i = 0; i < service_count; ++i) {
free(services[i].name);
}
return 0;
}
The Service Locator pattern provides a central point of access to various services within an application. Instead of objects directly creating or finding their dependencies, they request them from a global locator. This decouples the client from concrete implementations, increasing flexibility and testability.
The code below demonstrates a simple Service Locator in C++. The ServiceLocator class holds a map of service interfaces to their implementations. registerService adds a service, and resolve retrieves it. Clients request services via ServiceLocator::get(), which handles the lookup. This fits C++ style by utilizing RAII for the locator’s lifetime (though global scope is used here for simplicity) and using templates to provide type safety when resolving services. Using interfaces (abstract classes) and concrete implementations promotes loose coupling common in C++ designs.
#include <iostream>
#include <map>
#include <memory>
#include <stdexcept>
// Interface for services
class ILoggingService {
public:
virtual void log(const std::string& message) = 0;
virtual ~ILoggingService() = default;
};
// Concrete implementation of the logging service
class ConsoleLoggingService : public ILoggingService {
public:
void log(const std::string& message) override {
std::cout << "Log: " << message << std::endl;
}
};
// Service Locator class
class ServiceLocator {
public:
template <typename T>
T& get() {
auto it = services_.find(typeid(T));
if (it == services_.end()) {
throw std::runtime_error("Service not found");
}
return *std::any_cast<T>(it->second);
}
void registerService(const std::type_info& type, std::any service) {
services_[type] = service;
}
private:
std::map<std::type_info, std::any> services_;
};
// Global Service Locator instance
ServiceLocator& locator = ServiceLocator::getInstance();
ServiceLocator& ServiceLocator::getInstance() {
static ServiceLocator instance;
return instance;
}
// Client class that uses the service
class Client {
public:
Client(ILoggingService& loggingService) : loggingService_(loggingService) {}
void doSomething() {
loggingService_.log("Client is doing something...");
}
private:
ILoggingService& loggingService_;
};
int main() {
// Register the logging service
locator.registerService(typeid(ILoggingService), std::make_any<ConsoleLoggingService>());
// Resolve and use the logging service
Client client(locator.get<ILoggingService>());
client.doSomething();
return 0;
}
The Service Locator pattern manages dependencies by providing a central point of access to services. Instead of a class directly creating or finding its dependencies, it asks the locator for them. This promotes loose coupling and makes it easier to switch implementations. The C# example utilizes a static class ServiceLocator to hold service registrations in a Dictionary. Resolve<T>() retrieves a service, creating a new instance if not already registered (using a default constructor). While dependency injection is generally preferred in modern C#, Service Locator can be useful in legacy systems or scenarios where complete control over dependency resolution isn’t possible upfront.
using System;
using System.Collections.Generic;
public interface IOperation
{
string Execute();
}
public class OperationA : IOperation
{
public string Execute() => "Operation A Executed";
}
public class OperationB : IOperation
{
public string Execute() => "Operation B Executed";
}
public static class ServiceLocator
{
private static readonly Dictionary<Type, object> services = new();
public static void Register<T>(T service) => services[typeof(T)] = service;
public static T Resolve<T>() => (T)(services.ContainsKey(typeof(T)) ? services[typeof(T)] : Activator.CreateInstance(typeof(T)));
}
public class Client
{
public void RunOperation(string operationType)
{
IOperation operation;
switch (operationType)
{
case "A":
operation = ServiceLocator.Resolve<IOperation>();
break;
case "B":
operation = ServiceLocator.Resolve<IOperation>();
break;
default:
throw new ArgumentException("Invalid operation type");
}
Console.WriteLine(operation.Execute());
}
}
public class Program
{
public static void Main(string[] args)
{
ServiceLocator.Register(new OperationA());
ServiceLocator.Register<IOperation>(new OperationB()); //Register explicitly with interface
var client = new Client();
client.RunOperation("A");
client.RunOperation("B");
}
}
The Service Locator pattern provides a centralized way to access various services within an application. Instead of a service directly creating or relying on its dependencies, it requests them from a central registry (the Service Locator). This decouples the service from concrete implementations, making testing and configuration more flexible.
The TypeScript code below implements a simple Service Locator. Services are registered with the locator using a string key. When a service is needed, it is requested by its key. The use of a class and a static method is idiomatic TypeScript for this singleton-like access capability. Type safety is maintained through TypeScript’s type system by specifying the service types during registration and retrieval.
// service-locator.ts
interface ServiceMap {
[key: string]: any;
}
class ServiceLocator {
private static instance: ServiceLocator;
private services: ServiceMap = {};
private constructor() {
// Singleton pattern
}
static getInstance(): ServiceLocator {
if (!ServiceLocator.instance) {
ServiceLocator.instance = new ServiceLocator();
}
return ServiceLocator.instance;
}
registerService<T>(key: string, service: T): void {
this.services[key] = service;
}
getService<T>(key: string): T {
if (!this.services[key]) {
throw new Error(`Service not registered: ${key}`);
}
return this.services[key];
}
}
// Example Services
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`LOG: ${message}`);
}
}
interface DataService {
getData(): string;
}
class RealDataService implements DataService {
getData(): string {
return "Real data from the source!";
}
}
// Usage
const locator = ServiceLocator.getInstance();
locator.registerService<Logger>("logger", new ConsoleLogger());
locator.registerService<DataService>("dataService", new RealDataService());
const logger = locator.getService<Logger>("logger");
const dataService = locator.getService<DataService>("dataService");
logger.log(dataService.getData());
The Service Locator pattern provides a centralized way to obtain access to various services within an application. Instead of directly creating dependencies, components request them from a central “locator” object. This promotes loose coupling and makes it easier to swap implementations or manage dependencies, particularly in testing.
The JavaScript implementation uses a simple object to store service registrations and a resolve method to retrieve them. Services are registered with a key (string identifier). The resolve method returns the registered service for a given key, or throws an error if not found. This approach is idiomatic JavaScript due to its flexibility with objects and function-based programming. It avoids the complexity of a full-fledged dependency injection container while still offering dependency management benefits.
/**
* Service Locator Pattern in JavaScript
*/
class ServiceLocator {
constructor() {
this.services = {};
}
register(key, service) {
if (this.services[key]) {
console.warn(`Service with key "${key}" already registered. Overwriting.`);
}
this.services[key] = service;
}
resolve(key) {
if (!this.services[key]) {
throw new Error(`Service "${key}" not found.`);
}
return this.services[key];
}
}
// Example Services
class DatabaseService {
getData() {
return "Data from the database";
}
}
class LoggingService {
log(message) {
console.log(`LOG: ${message}`);
}
}
// Usage
const locator = new ServiceLocator();
locator.register("database", new DatabaseService());
locator.register("logger", new LoggingService());
const database = locator.resolve("database");
const logger = locator.resolve("logger");
logger.log(database.getData()); // Output: LOG: Data from the database
The Service Locator pattern provides a centralized way to access objects (services) without tightly coupling the client code to their concrete implementations. Instead of directly constructing dependencies, a client requests them from a central “locator”. This promotes loose coupling and makes it easier to swap implementations or configure the application. The Python implementation uses a dictionary to store services, keyed by their interface (typically a class or abstract base class) and returning the configured instance when requested. This is a practical approach for Python, relying on dynamic typing and the flexibility of dictionaries for dependency management, avoiding complex abstract factory setups when not strictly necessary.
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
def connect(self):
print(f"Connecting to database: {self.connection_string}")
class Logger:
def __init__(self, log_file):
self.log_file = log_file
def log(self, message):
print(f"Logging to {self.log_file}: {message}")
class ServiceLocator:
def __init__(self):
self._services = {}
def register(self, interface, implementation):
self._services[interface] = implementation
def resolve(self, interface):
if interface in self._services:
return self._services[interface]
else:
return None # Or raise an exception
# Usage
locator = ServiceLocator()
locator.register(DatabaseConnection, DatabaseConnection("my_db_string"))
locator.register(Logger, Logger("app.log"))
def client_code(db_conn, logger):
db_conn.connect()
logger.log("Client code executed.")
db_conn = locator.resolve(DatabaseConnection)
logger = locator.resolve(Logger)
if db_conn and logger:
client_code(db_conn, logger)
else:
print("Dependencies not found!")
The Service Locator pattern provides a centralized way to obtain references to services without tightly coupling the client code to the concrete service implementations. Instead of constructing dependencies directly, clients request them from a locator. This promotes loose coupling and makes it easier to swap service implementations.
The Java example implements a simple ServiceLocator class responsible for storing and retrieving services. Service is an interface that defines the functionality to be provided. Concrete services like EmailService and LoggingService implement this interface. Clients obtain services via ServiceLocator.getService(Service.class), which returns the appropriate implementation. This aligns with Java’s dependency injection principles, though it’s a less explicit form compared to using frameworks like Spring. The use of interfaces and a central access point is standard Java practice.
// Service Interface
interface Service {
void execute();
}
// Concrete Service 1
class EmailService implements Service {
@Override
public void execute() {
System.out.println("Sending email...");
}
}
// Concrete Service 2
class LoggingService implements Service {
@Override
public void execute() {
System.out.println("Logging data...");
}
}
// Service Locator
class ServiceLocator {
private static final java.util.Map<Class<? extends Service>, Service> services = new java.util.HashMap<>();
static {
// Register services
registerService(EmailService.class, new EmailService());
registerService(LoggingService.class, new LoggingService());
}
private static void registerService(Class<? extends Service> serviceType, Service service) {
services.put(serviceType, service);
}
public static <T extends Service> T getService(Class<T> serviceType) {
return (T) services.get(serviceType);
}
}
// Client Code
class Client {
private Service emailService;
private Service loggingService;
public Client() {
this.emailService = ServiceLocator.getService(EmailService.class);
this.loggingService = ServiceLocator.getService(LoggingService.class);
}
public void doSomething() {
emailService.execute();
loggingService.execute();
}
}
// Main Class to run the example
public class Main {
public static void main(String[] args) {
Client client = new Client();
client.doSomething();
}
}