Подтвердить что ты не робот

Зачем использовать услуги (IServiceProvider)?

Я прихожу к этому вопросу, изучая инфраструктуру XNA, но я бы хотел получить общее понимание.

ISomeService someService = (ISomeService)Game.GetServices(typeof(ISomeService));

а затем мы делаем что-то с любыми функциями/свойствами в интерфейсе:

someService.DoSomething();  // let say not a static method but doesn't matter

Я пытаюсь понять, почему такая реализация лучше, чем:

myObject = InstanceFromComponentThatWouldProvideTheService();

myObject.DoSomething();

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

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

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

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

4b9b3361

Ответ 1

Позвольте мне объяснить это с помощью примера из самой 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 и, таким образом, создаете какой-то sprite-batch- сервис, чтобы передать это. Это намного сложнее, чем вам нужно для игры, которая, как правило, не нуждается в проверке версий, сборочной зависимости или сторонних требований к расширяемости. Просто потому, что Game.Services существует, не означает, что вы должны его использовать! Если вы можете передать вещи (например, экземпляр SpriteBatch) непосредственно - просто сделайте это - это намного проще и понятнее.

Ответ 3

Интерфейсы понятнее и проще mock.

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

Ответ 4

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

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

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