-
Notifications
You must be signed in to change notification settings - Fork 0
Service Locator
This game uses the Service Locator pattern to provide global access to some key services. ServiceLocator
(code) itself is a static class which can be accessed directly. For example:
GameTime gameTime = ServiceLocator.getTimeSource();
PhysicsService physics = ServiceLocator.getPhysicsService();
Accessible services are:
- Entity Service: Keeps track of all entities that exist in the game, updating them each frame. This has to be global so that each new entity can register itself.
- Physics Service: Keeps track of physics-enabled entities, updating them according to the physics simulation each frame.
- Render Service: Keeps track of rendered entities, drawing them each frame.
- Resource Service: Provides a way to load and access assets from anywhere, while ensuring that the same asset is not loaded more than once.
- Input Service: Provides access to user input.
- Time Source: The recommended way to get the current time or calculate how much time has passed. The time source can be mocked for unit tests to prevent tests being time-dependant.
Singletons are generally advised again and considered as anti-patterns, due to their drawbacks:
- Providing global access to classes around the code base decreases readability and leads to bugs. Any function can have the side effect of modifying external state. Service locator still has this problem, but limits which classes are globally accessible and restricts access through 1 place.
- Singletons encourage excessive coupling to global components.
- We don't necessarily want to restrict ourselves to a single instance of a class. For example, we might be tempted to use a singleton for the game's input. If we then want to add support for local multi-player, we have to refactor code all over the code base where the singleton was used.
- When 'lazy-loaded' (i.e. instantiated on first access rather than at the start of application), we give up some control of the lifecycle of that class. Service locator instead lets us choose exactly when we want to initialize our services.
Dependency injection (DI) is a great way to keep code decoupled without relying on global instances, and avoids many of the problems listed above. However, DI can be difficult to learn and understand, since inversion of control goes against the intuitive ideas of abstraction and encapsulation. You are highly encouraged to use this pattern in the future, but for simplicity it is not heavily used in the game (other than when exposing dependencies for unit testing).