19

I'm coming to this question from exploring the XNA framework, but I'd like a general understanding.

ISomeService someService = (ISomeService)Game.GetServices(typeof(ISomeService));

and then we do something with whatever functions/properties are in the interface:

someService.DoSomething();  // let's say not a static method but doesn't matter

I'm trying to figure out why this kind of implementation is any better than:

myObject = InstanceFromComponentThatWouldProvideTheService();

myObject.DoSomething();

When you use the services way to get your interface, you're really just getting an instance of the component that provides the service anyway. Right? You can't have an interface "instance". And there's only one class that can be the provider of a service. So all you really have is an instance of your component class, with the only difference being that you only have access to a subset of the component object (whatever subset is in the interface).

How is this any different from just having public and private methods and properties? In other words, the public methods/properties of the component is the "interface", and we can stop with all this roundaboutness. You can still change how you implement that "interface" without breaking anything (until you change the method signature, but that would break the services implementation too).

And there is going to be a 1-to-1 relationship between the component and the service anyway (more than one class can't register to be a provider of the service), and I can't see a class being a provider of more than one service (srp and all that).

So I guess I'm trying to figure out what problem this kind of framework is meant to solve. What am I missing?

idlewire
  • 499
  • 1
  • 3
  • 11

4 Answers4

19

Allow me to explain it via an example from XNA itself:

The ContentManager constructor takes a IServiceProvider. It then uses that IServiceProvider to get a IGraphicsDeviceService, which it in turn uses to get a GraphicsDevice onto which it loads things like textures, effects, etc.

It cannot take a Game - because that class is entirely optional (and is in a dependent assembly). It cannot take a GraphicsDeviceManager (the commonly used implementation of IGraphicsDeviceService) because that, like Game is an optional helper class for setting up the GraphicsDevice.

It can't take a GraphicsDevice directly, because you may be creating a ContentManager before the GraphicsDevice is created (this is exactly what the default Game class does). So it takes a service that it can retrieve a graphics device from later.

Now here is the real kicker: It could take a IGraphicsDeviceService and use that directly. BUT: what if at some time in the future the XNA team adds (for example) an AudioDevice class that some content types depend on? Then you'd have to modify the method signature of the ContentManager constructor to take an IAudioDeviceService or something - which will break third-party code. By having a service provider you avoid this issue.

In fact - you don't have to wait for the XNA team to add new content types requiring common resources: When you write a custom ContentTypeReader you can get access to the IServiceProvider from the content manager and query it for whatever service you like - even your own! This way your custom content types can use the same mechanism as the first-class XNA graphics types use, without the XNA code having to know about them or the services they require.

(Conversely, if you never load graphics types with your ContentManager, then you never have to provide it with a graphics device service.)

This is, of course, all well and good for a library like XNA, which needs to be updatable without breaking third-party code. Especially for something like ContentManager that is extendible by third parties.

However: I see lots of people running around using DrawableGameComponent, finding that you can't get a shared SpriteBatch into it easily, and so creating some kind of sprite-batch-service to pass that around. This is a lot more complication than you need for a game which generally has no versioning, assembly-dependency, or third-party extensibility requirements to worry about. Just because Game.Services exists, doesn't mean you have to use it! If you can pass things (like a SpriteBatch instance) around directly - just do that - it's much simpler and more obvious.

Andrew Russell
  • 26,924
  • 7
  • 58
  • 104
  • 3
    So would it be wrong to say that the service provider is like a dictionary, which maps services to class objects? In this way, it provides another layer of abstraction in that callers can specify the service name without knowing what class is going to implement it. However, the caller needs to know which methods it is going to be able to call on that object, hence the service interface... Am I right that this would be of limited use to a programmer/team that has complete control over its codebase? How useful might it be for re-use somewhere down the line? – idlewire Jul 24 '11 at 21:54
  • 1
    And it seems like this framework would allow you to determine which class is going to provide the service at runtime -- maybe you want to have that depend on a command-line parameter or user input... But the great majority of the time, you already _know_ at compiletime which class is registering as the provider of a service. Would it be safe to say that _if_ you are always going to know this, then this extra layer of abstraction might not be worthwhile? – idlewire Jul 24 '11 at 22:09
  • 3
    Correct. `IServiceProvider` is basically a dictionary of `Type` to `object`. But it is good practice for `Type` to be of an `interface` and that the `object` returned implements that interface. For your own internal reuse it's not especially useful - because you also have the ability to modify your code (unlike XNA itself, which you can't modify). The important point here (like for your command-line example) is that you don't have to use a services architecture at all! You just need to use an `interface`. `IServiceProvider` is just one (fairly complex) method for passing interfaces around. – Andrew Russell Jul 25 '11 at 00:10
  • I understand from this conversation that using something like a spritebatch service might not be the most standard way of doing things , but are there any significant disadvantages of doing so ? – angryInsomniac May 01 '12 at 09:54
  • 2
    @angryInsomniac There's no functional or performance disadvantage. It is, in my expert opinion, harder to maintain (especially as team size or game complexity increases). For example: What state will each component leave the service in for the next component? What if they change order? What if you need to actually do batching between components? What if you need to change draw modes? It is much easier to "see" these things if you pass the SpriteBatch object as an argument. – Andrew Russell May 01 '12 at 12:19
6

See http://en.wikipedia.org/wiki/Dependency_inversion_principle (and it's links) for a good start as to the architectural principles behind it

Eddy
  • 5,320
  • 24
  • 40
1

Interfaces are clearer and easier to mock.

That can be important, depending on your unit test policy.

Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
0

Using a service provider is also a way of better controlling what portions of your code have access to certain other portions of your code. Similarly to passing an object through your code, you can pass an IServiceProvider implementation through the code to specific modules. This would allow for those modules to access certain services that are accessible through the service provider.

You can have many classes implement the IServiceProvider interface, each of which could provide access to one or more services - they are not restricted to returning a single instance (whether that be to themselves or another object).

For example, a use may be to have an IServiceProvider that contains services for keyboard handling, mouse handling and AI algorithms. Passing this interface to different modules or managers within your code will allow those modules or managers to retrieve the services they require (such as an EnemyManager needing access to the AI service).

Samuel Slade
  • 8,405
  • 6
  • 33
  • 55
  • Hmm, but I don't see anything here for which the services solution is better. _Except_ for if a class was a provider of more than one service. Then, yes, you could control which portions of the class were accessible in a way that was more granular than simply public/private. But having this kind of "mega" class that provides more than one service doesn't seem like a good idea. – idlewire Jul 23 '11 at 20:18
  • **-1** Because this answer isn't really correct, and has some flawed advice. You shouldn't need to implement `IServiceProvider` if you can use `GameServiceContainer` - which you can get from `Game.Services`, so you also usually don't need or want several instances. And using services for "access control" might *sound* very grand, it doesn't actually add anything in practice. Information hiding is a sound principle - but you don't need a services architecture to implement it! Having good interfaces is more than sufficient (and I don't necessarily mean `interface`, either). – Andrew Russell Jul 24 '11 at 14:05
  • idlewire - The GameServiceContainer contains services used within the game. So it's a kind of central store of game services. @Andrew Russel - GameServiceContainer already implements the IServiceProvider interface. My comment on passing objects through as the IServiceProvider interface was merely an example of how it could be used - though this would probably be a more likely case within a public API or framework, as passing the GameServiceContainer would allow other code to add services, which is something you may not want. – Samuel Slade Jul 25 '11 at 08:33
  • @Slade That `GameServiceContainer` implements `IServiceProvider` was entirely my point - you won't need to create your own implementation. And you should pass that `GameServiceContainer` around as an `IServiceProvider`, simply because it is better encapsulated that way (it's architecturally "nice"). However it does not *robustly* protect your service container from having services added (an consumer of the `IServiceProvider` could trivially cast it back to a `GameServiceContainer`) - but rarely is that kind of robustness an actual requirement. – Andrew Russell Jul 25 '11 at 12:16
  • Apologies, I misunderstood what you were stating. I was also trying to highlight the fact that it could be passed across as that, but perhaps my explanation was not entirely clear. – Samuel Slade Jul 25 '11 at 13:39