为什么要使用服务(IServiceProvider)?
我是在探索 XNA 框架时提出这个问题的,但我想要一个总体的了解。
ISomeService someService = (ISomeService)Game.GetServices(typeof(ISomeService));
然后我们用接口中的任何函数/属性做一些事情:
someService.DoSomething(); // let's say not a static method but doesn't matter
我试图弄清楚为什么这种实现比:
myObject = InstanceFromComponentThatWouldProvideTheService();
myObject.DoSomething();
当您使用服务方式获取接口时,您实际上只是得到一个无论如何提供服务的组件的实例。正确的?您不能拥有接口“实例”。并且只有一个类可以成为服务的提供者。因此,您真正拥有的只是组件类的一个实例,唯一的区别是您只能访问组件对象的子集(无论接口中的子集是什么)。
这与仅拥有公共和私有方法和属性有什么不同?换句话说,组件的公共方法/属性就是“接口”,我们可以停止所有这些迂回。您仍然可以更改实现该“接口”的方式,而不会破坏任何内容(直到您更改方法签名,但这也会破坏服务实现)。
无论如何,组件和服务之间将存在一对一的关系(多个类不能注册为服务的提供者),并且我看不到一个类是服务的提供者不止一项服务(srp 等)。
所以我想我正在试图弄清楚这种框架要解决什么问题。我缺少什么?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
请允许我通过 XNA 本身的示例来解释它:
ContentManager
构造函数采用IServiceProvider
。然后,它使用该IServiceProvider
来获取IGraphicsDeviceService
,进而使用该IGraphicsDeviceService
来获取一个GraphicsDevice
,在该设备上加载纹理、效果等内容。等等。它不能采用
Game
- 因为该类完全是可选的(并且位于依赖程序集中)。它不能采用GraphicsDeviceManager
(IGraphicsDeviceService
的常用实现),因为像Game
一样,它是一个用于设置>图形设备
。它不能直接获取
GraphicsDevice
,因为您可能在创建GraphicsDevice
之前创建ContentManager
(这完全< /em> 默认Game
类的作用)。因此,它需要一个可以稍后从检索图形设备的服务。现在真正的问题是:它可以采用
IGraphicsDeviceService
并直接使用它。 但是:如果 XNA 团队在将来的某个时候添加(例如)某些内容类型所依赖的AudioDevice
类怎么办?然后,您必须修改ContentManager
构造函数的方法签名以获取IAudioDeviceService
或其他内容 - 这会破坏第三方代码。通过拥有服务提供商,您可以避免这个问题。事实上 - 您不必等待 XNA 团队添加需要公共资源的新内容类型:当您编写自定义
ContentTypeReader
时,您可以访问IServiceProvider
从内容管理器中查询您喜欢的任何服务 - 甚至是您自己的服务!这样,您的自定义内容类型就可以使用与一流 XNA 图形类型相同的机制,而 XNA 代码无需了解它们或它们所需的服务。(相反,如果您从不使用
ContentManager
加载图形类型,那么您就不必为其提供图形设备服务。)当然,这对于 < em>library如 XNA,需要在不破坏第三方代码的情况下进行更新。特别是对于像
ContentManager
这样可由第三方扩展的东西。但是:我看到很多人使用
DrawableGameComponent
,发现无法轻松地将共享SpriteBatch
放入其中,因此创建某种精灵批量服务来传递它。对于通常不需要担心版本控制、程序集依赖性或第三方可扩展性要求的游戏来说,这比您需要的要复杂得多。仅仅因为Game.Services
存在,并不意味着您必须使用它!如果您可以直接传递事物(例如SpriteBatch
实例) - 就这样做 - 它会更简单、更明显。Allow me to explain it via an example from XNA itself:
The
ContentManager
constructor takes aIServiceProvider
. It then uses thatIServiceProvider
to get aIGraphicsDeviceService
, which it in turn uses to get aGraphicsDevice
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 aGraphicsDeviceManager
(the commonly used implementation ofIGraphicsDeviceService
) because that, likeGame
is an optional helper class for setting up theGraphicsDevice
.It can't take a
GraphicsDevice
directly, because you may be creating aContentManager
before theGraphicsDevice
is created (this is exactly what the defaultGame
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) anAudioDevice
class that some content types depend on? Then you'd have to modify the method signature of theContentManager
constructor to take anIAudioDeviceService
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 theIServiceProvider
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 sharedSpriteBatch
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 becauseGame.Services
exists, doesn't mean you have to use it! If you can pass things (like aSpriteBatch
instance) around directly - just do that - it's much simpler and more obvious.请参阅 http://en.wikipedia.org/wiki/Dependency_inversion_principle (及其链接)了解其背后的架构原则是一个良好的开端
See http://en.wikipedia.org/wiki/Dependency_inversion_principle (and it's links) for a good start as to the architectural principles behind it
界面更清晰,更容易模拟。
这可能很重要,具体取决于您的单元测试策略。
Interfaces are clearer and easier to mock.
That can be important, depending on your unit test policy.
使用服务提供程序也是更好地控制代码的哪些部分可以访问代码的某些其他部分的一种方法。与通过代码传递对象类似,您可以通过代码将 IServiceProvider 实现传递到特定模块。这将允许这些模块访问可通过服务提供商访问的某些服务。
您可以让许多类实现 IServiceProvider 接口,每个类都可以提供对一个或多个服务的访问 - 它们不限于返回单个实例(无论是对于它们自己还是另一个对象)。
例如,一个用途可能是拥有一个 IServiceProvider,其中包含键盘处理、鼠标处理和 AI 算法的服务。将此接口传递给代码中的不同模块或管理器将允许这些模块或管理器检索它们所需的服务(例如需要访问 AI 服务的 EnemyManager)。
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).