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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…