为什么要使用服务(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和所有)。
所以我想我想弄清楚这种框架要解决什么问题。 我错过了什么?
请允许我通过XNA本身的示例来解释它:
ContentManager
构造函数采用IServiceProvider
。 然后它使用该IServiceProvider
来获取IGraphicsDeviceService
,它又用于获取GraphicsDevice
,它会加载纹理,效果等内容。
它不能用于Game
– 因为该类完全是可选的(并且在依赖程序集中)。 它不能采用GraphicsDeviceManager
(常用的IGraphicsDeviceService
实现),因为它像Game
一样是一个用于设置GraphicsDevice
的可选助手类。
它不能直接使用GraphicsDevice
,因为您可能在创建GraphicsDevice
之前创建了一个ContentManager
(这正是默认的Game
类所做的)。 因此,它需要一项服务,它可以从以后检索图形设备。
现在这里是真正的踢球者:它可以采用IGraphicsDeviceService
并直接使用它。 但是 :如果在未来的某个时间XNA团队添加(例如)一些内容类型依赖的AudioDevice
类会怎么样? 然后,您必须修改ContentManager
构造函数的方法签名以获取IAudioDeviceService
或其他东西 – 这将破坏第三方代码。 通过拥有服务提供商,您可以避免此问题。
实际上 – 您不必等待XNA团队添加需要公共资源的新内容类型:当您编写自定义ContentTypeReader
您可以从内容管理器访问IServiceProvider
并查询它以获得您喜欢的任何服务 – 甚至你自己! 这样,您的自定义内容类型可以使用与第一类XNA图形类型相同的机制,而XNA代码无需了解它们或所需的服务。
(相反,如果您从未使用ContentManager
加载图形类型,那么您永远不必为其提供图形设备服务。)
当然,对于像XNA这样的库来说,这一切都很好,这需要在不破坏第三方代码的情况下进行更新。 特别适用于可由第三方扩展的ContentManager
。
但是:我看到很多人在使用DrawableGameComponent
跑来跑去,发现你不能轻易地将共享的SpriteBatch
放入其中,因此创建某种sprite-batch-service来传递它。 这比通常没有版本控制,程序集依赖性或第三方可扩展性要求的游戏所需要的复杂程度要高得多。 仅仅因为Game.Services
存在,并不意味着你必须使用它! 如果你可以直接传递东西(比如SpriteBatch
实例) – 就这样做 – 它更简单,更明显。
请参阅http://en.wikipedia.org/wiki/Dependency_inversion_principle (及其链接),了解其背后的架构原则。
接口更清晰,更容易模拟 。
这可能很重要,具体取决于您的unit testing政策。
使用服务提供程序也可以更好地控制代码的哪些部分可以访问代码的某些其他部分。 与通过代码传递对象类似,您可以通过代码将IServiceProvider实现传递给特定模块。 这将允许这些模块访问可通过服务提供商访问的某些服务。
您可以让许多类实现IServiceProvider接口,每个接口都可以提供对一个或多个服务的访问 – 它们不限于返回单个实例(无论是自身还是其他对象)。
例如,一个用途可能是拥有一个IServiceProvider,它包含用于键盘处理,鼠标处理和AI算法的服务。 将此接口传递给代码中的不同模块或管理器将允许这些模块或管理器检索它们所需的服务(例如需要访问AI服务的EnemyManager)。