Зачем пользоваться услугами (IServiceProvider)? - PullRequest
17 голосов
/ 23 июля 2011

Я подхожу к этому вопросу, изучая инфраструктуру 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();

Когда вы используете сервисный способ для получения вашего интерфейса, вы действительно простов любом случае получить экземпляр компонента, который предоставляет сервис.Правильно?У вас не может быть интерфейса "экземпляр".И есть только один класс, который может быть поставщиком услуг.Таким образом, все, что у вас есть, - это экземпляр класса вашего компонента, с той лишь разницей, что вы имеете доступ только к подмножеству объекта компонента (независимо от того, какое подмножество находится в интерфейсе).

Чем это отличается?просто иметь открытые и закрытые методы и свойства?Другими словами, открытые методы / свойства компонента являются «интерфейсом», и мы можем остановиться на всей этой окольности.Вы все еще можете изменить способ реализации этого «интерфейса», ничего не нарушая (пока вы не измените сигнатуру метода, но это также нарушит реализацию служб).

И будет 1-к-1В любом случае, отношения между компонентом и службой (более одного класса не могут зарегистрироваться в качестве поставщика службы), и я не вижу класса, являющегося поставщиком более чем одной службы (srp и все такое).

Так что, я думаю, я пытаюсь выяснить, какую проблему призван решить этот вид фреймворка.Чего мне не хватает?

Ответы [ 4 ]

17 голосов
/ 24 июля 2011

Позвольте мне объяснить это на примере самой XNA:

Конструктор ContentManager занимает IServiceProvider. Затем он использует это IServiceProvider, чтобы получить IGraphicsDeviceService, которое он, в свою очередь, использует, чтобы получить GraphicsDevice, в который он загружает такие вещи, как текстуры, эффекты и т. Д.

Он не может принимать Game - потому что этот класс является полностью необязательным (и находится в зависимой сборке). Он не может принимать GraphicsDeviceManager (обычно используемая реализация IGraphicsDeviceService), потому что, подобно Game, это необязательный вспомогательный класс для установки GraphicsDevice.

Он не может принимать GraphicsDevice напрямую, поскольку вы, возможно, создаете ContentManager до создания GraphicsDevice (это точно , что делает класс Game по умолчанию). Таким образом, требуется служба, которая может извлечь графическое устройство из позднее .

Теперь вот настоящий кикер: Это может взять IGraphicsDeviceService и использовать его напрямую. НО : что если в будущем команда XNA добавит (например) класс AudioDevice, от которого зависят некоторые типы контента? Затем вам придется изменить сигнатуру метода конструктора ContentManager, чтобы он занял IAudioDeviceService или что-то еще, что нарушит сторонний код. Имея поставщика услуг, вы избежите этой проблемы.

На самом деле - вам не нужно ждать, пока команда XNA добавит новые типы контента, требующие общих ресурсов: когда вы пишете пользовательский ContentTypeReader, вы можете получить доступ к IServiceProvider из менеджера контента и запросить его за любую услугу, которая вам нравится - даже вашу собственную! Таким образом, ваши пользовательские типы контента могут использовать тот же механизм, что и первоклассные графические типы XNA, без необходимости знать код XNA о них или об услугах, которые им требуются.

(И наоборот, если вы никогда не загружаете типы графики с помощью ContentManager, вам никогда не придется предоставлять ему услугу графического устройства.)

Это, конечно, хорошо для библиотеки , такой как XNA, которую нужно обновлять, не нарушая сторонний код. Особенно для чего-то вроде ContentManager, которое может быть распространено третьими лицами.

Однако: Я вижу, как много людей бегают, используя DrawableGameComponent, находя, что вы не можете легко вставить в него общий SpriteBatch, и, таким образом, создавая какой-то сервис-спрайт чтобы передать это. Это намного сложнее, чем нужно для игры, в которой обычно не нужно беспокоиться о версиях, зависимости от сборки или сторонних расширениях. То, что существует Game.Services, не означает, что вы должны его использовать! Если вы можете передавать вещи (например, SpriteBatch экземпляр) напрямую - просто сделайте это - это намного проще и очевиднее.

6 голосов
/ 23 июля 2011

См. http://en.wikipedia.org/wiki/Dependency_inversion_principle (и его ссылки) для хорошего начала относительно архитектурных принципов, стоящих за ним

1 голос
/ 23 июля 2011

Интерфейсы понятнее и проще для макета .

Это может быть важно, в зависимости от вашей модульной проверки политики.

0 голосов
/ 23 июля 2011

Использование поставщика услуг также позволяет лучше контролировать, какие части вашего кода имеют доступ к определенным другим частям вашего кода. Подобно передаче объекта через ваш код, вы можете передать реализацию IServiceProvider через код в конкретные модули. Это позволило бы тем модулям получать доступ к определенным службам, доступным через поставщика услуг.

У вас может быть много классов, реализующих интерфейс IServiceProvider, каждый из которых может предоставлять доступ к одной или нескольким службам - они не ограничены возвратом одного экземпляра (будь то для себя или другого объекта).

Например, использование может состоять в том, чтобы иметь IServiceProvider, который содержит сервисы для обработки клавиатуры, обработки мыши и алгоритмов AI. Передача этого интерфейса различным модулям или менеджерам в вашем коде позволит этим модулям или менеджерам извлекать необходимые им службы (например, EnemyManager, которому необходим доступ к службе AI).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...