为什么要使用服务(IServiceProvider)?

时间:2011-07-23 17:27:59

标签: c# xna

我是从探索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及其所有)。

所以我想我正在试图找出这种框架要解决的问题。我错过了什么?

4 个答案:

答案 0 :(得分:17)

请允许我通过XNA本身的一个例子来解释它:

ContentManager构造函数需要IServiceProvider。然后,它使用IServiceProvider来获取IGraphicsDeviceService,然后它会用来获取GraphicsDevice,它会加载纹理,效果等内容。

它不能使用Game - 因为该类完全是可选的(并且在依赖程序集中)。它不能使用GraphicsDeviceManagerIGraphicsDeviceService的常用实现),因为Game是一个可选的辅助类,用于设置GraphicsDevice

不能直接使用GraphicsDevice,因为您可能在创建ContentManager之前创建GraphicsDevice(这完全是默认值Game班级确实)。因此,它需要一项服务,它可以从稍后检索图形设备

现在这里是真正的踢球者: 可以取一个IGraphicsDeviceService并直接使用它。 但是:如果在将来的某个时间XNA团队添加(例如)某些内容类型所依赖的AudioDevice类,该怎么办?然后,您必须修改ContentManager构造函数的方法签名以获取IAudioDeviceService或其他东西 - 这将破坏第三方代码。通过拥有服务提供商,您可以避免此问题。

事实上 - 您不必等待XNA团队添加需要公共资源的新内容类型:撰写自定义ContentTypeReader时,您可以从内容中访问IServiceProvider经理并查询您喜欢的任何服务 - 甚至是您自己的服务!这样,您的自定义内容类型可以使用与第一类XNA图形类型相同的机制,而XNA代码无需了解它们或所需的服务。

(相反,如果您从未使用ContentManager加载图形类型,则无需为其提供图形设备服务。)

当然,这对于像XNA这样的 一切都很好,这需要在不破坏第三方代码的情况下进行更新。特别是对于ContentManager这样可以由第三方扩展的东西。

但是:我看到有很多人在使用DrawableGameComponent,并发现您无法轻松获得共享SpriteBatch,因此创建了某种sprite-batch-service来传递它。这比通常没有版本控制,程序集依赖性或第三方可扩展性要求的游戏所需的复杂程度要高得多。仅仅因为Game.Services存在,并不意味着你必须使用它!如果可以直接传递内容(例如SpriteBatch实例) - 只需这样做 - 它就会更简单,更明显。

答案 1 :(得分:6)

请参阅http://en.wikipedia.org/wiki/Dependency_inversion_principle(及其链接),了解其背后的架构原则

答案 2 :(得分:1)

接口更清晰,更容易mock

这可能很重要,具体取决于您的unit test政策。

答案 3 :(得分:0)

使用服务提供商也可以更好地控制代码的哪些部分可以访问代码的某些其他部分。与通过代码传递对象类似,您可以通过代码将IServiceProvider实现传递给特定模块。这将允许这些模块访问可通过服务提供商访问的某些服务。

您可以让许多类实现IServiceProvider接口,每个接口都可以提供对一个或多个服务的访问 - 它们不限于返回单个实例(无论是自身还是其他对象)。

例如,一个用途可能是拥有一个IServiceProvider,它包含用于键盘处理,鼠标处理和AI算法的服务。将此接口传递给代码中的不同模块或管理器将允许这些模块或管理器检索所需的服务(例如需要访问AI服务的EnemyManager)。