Written by: Saturday, February 12, 2011
The service provider pattern is one of my favorite patterns: It creates clean, slick code when used right - but when used in the wrong places it creates libraries that are hard to use, difficult to understand and a nightmare to maintain. In this blog post we examine the service provider pattern, related concepts, like global variables, services, managers, service providers, contexts and how they can be used in games.
The problem is simple:
I have a piece of code. The code depends on data and functions outside of my own code. How can my code access the external dependencies?
Let's work with this practical example:
I am coding a "Camera" class. The camera can be moved with the WASD keys on the keyboard - therefore, we need access to the keyboard to test if the WASD keys are pressed. The camera should move with a constant velocity. If the game uses a variable time step for each frame, then we need to know the size of the time step for the current frame. With this information, we can scale the movement in each frame.
In short:
Our Camera code depends on external data (time step size) and on an external function that checks if a keyboard key is pressed. How does the Camera code access the external data and functions?
The simplest solutions is to use global variables and global methods:
public class Game { // Size of the current time step aka "elapsed time". public static float DeltaTime; ...} public class InputManager { // Checks if a key is pressed. public static bool IsKeyDown(Keys key) { … } ...}
Everything that our Camera code needs is public and readily available. - This solution is simple, but has its drawbacks. For example:
In XNA a similar solution is used for device input classes in XNA (e.g. the Keyboard class). But the solution looks very clumsy for the global DeltaTime variable. Let's look for alternatives.
We can also pass the required dependencies as parameters to the Camera when it is updated:
public class Camera : GameComponent { public void Update(float deltaTime, InputManager input) { ... } ...}
This looks natural for the time parameter, and in XNA this pattern is used to pass the time to GameComponents.
And what if other game components need more functionality? Should we add more and more parameters? - Instead, we can group all stuff into one object and call it a Context:
public class UpdateContext { public float DeltaTime; public bool IsGameActive; public InputManager Input; public SpriteBatch SpriteBatch; ... } public class Camera : GameComponent { public void Update(UpdateContext context) { ... } ...}
This has the advantage that the method has only a single parameter. The disadvantage is that the context must be extended whenever a game component needs a new piece of data or a new functions. And we have to modify the GameComponent base class. If we want to use the XNA GameComponent class or any other third party framework, we cannot change the signature of the Update() method.
But even if we use a third party GameComponent base class, we can add a property very easily:
public class Camera : GameComponent { public UpdateContext UpdateContext { get; set;} public void Update() { ... } ...}
This solves the problem, but seems more like workaround. For the user of the camera game component, it is not obvious that the UpdateContext must be set before each Update() call. It is also not obvious that the UpdateContext is only used by the Update() method (and no other Camera method). Such an API demands a lot of explanation.
Properties are better used for obvious tasks and dependencies. For example, the XNA GameComponent class has a reference to the Game class that is set in the constructor:
public class GameComponent { public Game Game { get; } public GameComponent(Game game) { ... } ...}
This is a good use for a local property. It makes the data and functionality of the Game class available for all GameComponents. And the Game is essential for the whole GameComponent class (not just the Update() method).
A pattern that solves several problems mentioned above is the service provider pattern - also called service locator pattern. The .NET Framework provides the interface IServiceProvider to support this pattern:
public interface IServiceProvider { object GetService(Type serviceType);}
A service provider is basically a dictionary or a registry that manages a set of objects that provide certain services. A good naming convention is to call the interface of a service IXyzService. Most of the time, the class that implements the interface is called XyzManager. For example:
public interface IInputService { bool IsKeyDown(Keys key); ...} public class InputManager : IInputService { public bool IsKeyDown(Keys key); public void Update(); ...}
The service interface defines the data and functions that are available to the game components. The manager class defines additional members, like properties to configure the manager or the Update() method. The module that creates the InputManager (usually the Game class) is responsible for configuring the manager and for calling Update() once per frame. In this method the manager can do its work - if there is any. Such members are excluded from the IInputService interface because the interface shows only the member that are relevant for the clients of the service.
We can put the service provider into a global variable, e.g.
public class Game { public static IServiceProvider Services { get; }}
Then the camera can use it like this:
public class Camera : GameComponent { public void Update() { var gameTimeService = (IGameTimeService)Game.Services.GetService(typeof(IGameTimeService); float deltaTime = gameTimeService.DeltaTime; var inputService = (IInputService)Game.Services.GetService(typeof(IInputService)); if (inputService.IsKeyDown(Keys.A)) ... }}
We do not have to bloat the GameComponent with properties or parameters for all possible services. And the dependencies are not hardcoded: The InputService could be a DefaultInputManager that checks the normal keyboard. It could be a PlaybackInputManager that plays back recorded key events. Or another InputManager that reads input from and on-screen keyboard - our Camera class does not care.
The service provider pattern belongs to the Inversion of Control (IoC) patterns. Dependency injection is another IoC pattern. You can read more about that in Martin Fowler's article: http://martinfowler.com/articles/injection.html
Here is a list of dependendy injection frameworks for .NET: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
Dependency injection has its uses, and we use it too in some of our applications. But I must admit that I am not a big fan of this pattern and will not go into more details in this post.
Which pattern should be used in a game? - All of them! And I mean it. We have a game framework including game editor components where every pattern discussed above is used (even dependency injection). Each pattern has its place. And here are a few rules that have served us well in the last years:
The service provider pattern is very powerful, but must be used with care. In the next posts we will show how to implement the service provider pattern, how the DigitalRune Libraries support the service provider pattern and how the pattern can be used to create very clean and concise XNA game classes.
Martin Fowler's IoC article: http://martinfowler.com/articles/injection.html Description of the service locator pattern: http://gameprogrammingpatterns.com/service-locator.html List of Dependency Injection frameworks: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
0 comment(s) so far...
A collection of the most useful blog articles can be found here:
Article Collection (on Documentation page)
DigitalRune is a trademark of Garstenauer Information Technology OG.
Garstenauer Information Technology OG Weingartenstrasse 35, 4452 Ternberg Austria (EUROPE) office@digitalrune.com