Service locator and anti patterns
Service Locator and Anti-Patterns Deep Dive¶
Overview¶
Service Locator can bootstrap dependency access quickly, but it often hides coupling and weakens architectural clarity.
Core Concepts¶
- global or scoped registry returns dependencies at runtime
- call sites hide concrete dependency requirements
- implicit wiring increases runtime failure risk
- testing often needs registry mutation/reset behavior
Layer Responsibilities¶
- Locator layer:
- stores and returns object instances/providers
- Consumers:
- pull dependencies rather than receive them explicitly
- Test layer:
- overrides/replaces registry entries for test isolation
Data Flow¶
- Consumer requests service from locator.
- Locator resolves key/provider.
- Dependency is returned and used.
- Missing/incorrect registration fails at runtime.
Internal Architecture¶
Why this degrades over time:
- hidden dependency graph at call sites
- hard-to-track lifecycle ownership
- mutation-heavy test setup
- implicit global state leakage
Controlled usage can be acceptable in tiny apps or transitional migration phases.
Code Examples¶
object ServiceLocator {
lateinit var analytics: Analytics
}
class HomeViewModel : ViewModel() {
fun trackOpen() {
ServiceLocator.analytics.log("home_open")
}
}
Common Interview Questions¶
- Q: Why is constructor DI generally preferred? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.
- Q: Is Service Locator always an anti-pattern? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.
- Q: How do hidden dependencies hurt testability? A: Answer by defining boundaries and ownership first, then place business rules in the correct layer, and finish with testability and change-resilience tradeoffs.
- Q: How would you migrate incrementally to DI? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.
Production Considerations¶
- avoid introducing new locator-based dependencies
- migrate hotspots first (core services, ViewModels)
- keep temporary adapters during transition
- add static checks to block direct global locator usage
Scalability Tradeoffs¶
- Pros:
- fast startup for simple prototypes
- low initial ceremony
- Cons:
- hidden coupling and brittle tests
- runtime misconfiguration failures
- poor fit for multi-team codebases
Senior-Level Insights¶
Senior candidates should present migration strategy, not just anti-pattern labeling. Good answers discuss risk-managed refactors and compatibility bridges while moving toward explicit DI.