ServiceContainer, IoC и одноразовые предметы - PullRequest
7 голосов
/ 17 февраля 2009

У меня есть вопрос, и я собираюсь пометить это субъективно , поскольку, как мне кажется, это превращается в дискуссию. Я надеюсь на некоторые хорошие идеи или провокаторов. Я прошу прощения за многословный вопрос, но вы должны знать контекст.

Вопрос в основном:

  • Как вы относитесь к конкретным типам по отношению к контейнерам IoC? В частности, кто отвечает за их удаление, если они требуют утилизации и как эти знания распространяются на вызывающий код?

Требуется ли, чтобы они были идентифицируемыми? Если нет, то является ли этот код перспективным, или вы не можете использовать одноразовые предметы? Если вы применяете IDisposable-требования к интерфейсам и конкретным типам для обеспечения будущего, кто несет ответственность за объекты, внедренные как часть вызовов конструктора?


Редактировать : я принял ответ @ Крис Баллард , так как он наиболее близок к подходу, с которым мы в итоге пришли.

По сути, мы всегда возвращаем тип, который выглядит следующим образом:

public interface IService<T> : IDisposable
    where T: class
{
    T Instance { get; }
    Boolean Success { get; }
    String FailureMessage { get; } // in case Success=false
}

Затем мы возвращаем объект, реализующий этот интерфейс, как из .Resolve, так и .TryResolve, так что то, что мы получаем в вызывающем коде, всегда имеет один и тот же тип.

Теперь объект, реализующий этот интерфейс, IService<T> является IDisposable, и всегда должен быть удален. Программист не может решить, должен ли объект IService<T> быть удален или нет.

Однако, и это важная часть, должен ли экземпляр службы быть удален или нет, что знания запекаются в объект, реализующий IService<T>, так что если это сервис с фабричной областью (т. Е. Каждый вызов Resolve заканчивается с новым экземпляром службы), тогда экземпляр службы будет удален при удалении объекта IService<T>.

Это также позволило поддерживать другие специальные области, такие как пул. Теперь мы можем сказать, что нам нужно минимум 2 экземпляра службы, максимум 15 и обычно 5, что означает, что каждый вызов .Resolve будет либо извлекать экземпляр службы из пула доступных объектов, либо создавать новый. И затем, когда объект IService<T>, который содержит объединенную службу, удаляется, экземпляр службы освобождается обратно в его пул.

Конечно, это заставило весь код выглядеть так:

using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
    service.Instance.DoSomething();
}

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


Далее следует оригинальный вопрос для потомков


Длинный вопрос приходит сюда:

У нас есть контейнер IoC, который мы используем, и недавно мы обнаружили, что составляет проблему.

В коде, отличном от IoC, когда мы хотели использовать, скажем, файл, мы использовали такой класс:

using (Stream stream = new FileStream(...))
{
    ...
}

Не было никаких сомнений относительно того, был ли этот класс чем-то, что содержало ограниченный ресурс, или нет, так как мы знали, что файлы должны быть закрыты, и сам класс реализовал IDisposable. Правило состоит в том, что каждый класс, из которого мы создаем объект, реализующий IDisposable, должен быть удален. Никаких вопросов не было задано. Пользователь этого класса не может решить, является ли вызов Dispose необязательным или нет.

Хорошо, давайте перейдем к первому шагу в направлении контейнера IoC. Давайте предположим, что мы не хотим, чтобы код говорил напрямую с файлом, а вместо этого пройдем один уровень косвенности. Давайте назовем этот класс BinaryDataProvider для этого примера. Внутри класса используется поток, который все еще является одноразовым объектом, поэтому приведенный выше код будет изменен на:

using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
    ...
}

Это мало что меняет. Знание того, что класс реализует IDisposable, все еще здесь, без вопросов, нам нужно вызвать Dispose.

Но давайте предположим, что у нас есть классы, предоставляющие данные, которые сейчас не используют такие ограниченные ресурсы.

Приведенный выше код может быть записан как:

BinaryDataProvider provider = new BinaryDataProvider();
...

Хорошо, пока все хорошо, но здесь идет главный вопрос. Предположим, мы хотим использовать контейнер IoC для внедрения этого провайдера вместо зависимости от конкретного конкретного типа.

Тогда код будет:

IBinaryDataProvider provider =
    ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...

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

С учетом вышеуказанного изменения, что если мы позже захотим использовать объект, от которого действительно нужно избавиться? Ни один из существующих кодов, разрешающих этот интерфейс, не написан для удаления объекта, и что теперь?

Как мы видим, мы должны выбрать одно решение:

  • Реализация проверки во время выполнения, которая проверяет, что если конкретный тип, который регистрируется, реализует IDisposable, требует, чтобы интерфейс, через который он открывается, также реализовывал IDisposable. Это не хорошее решение
  • Прежде чем накладывать ограничения на используемые интерфейсы, они всегда должны наследоваться от IDisposable, чтобы быть ориентированными на будущее
  • Обеспечить выполнение, чтобы никакие конкретные типы не могли быть IDisposable, так как это специально не обрабатывается кодом, использующим контейнер IoC
  • Просто оставьте это на усмотрение программиста, чтобы проверить, реализует ли объект IDisposable и "делать правильные вещи"?
  • Есть ли другие?

Кроме того, как насчет внедрения объектов в конструкторы? Наш контейнер и некоторые другие контейнеры, которые мы рассмотрели, способны вводить свежий объект в параметр в конструктор конкретного типа. Например, если нашему BinaryDataProvider нужен объект, который реализует интерфейс ILogging, если мы применяем IDispose- «способность» к этим объектам, кто несет ответственность за удаление объекта регистрации?

Что вы думаете? Я хочу мнения, хорошие и плохие.

Ответы [ 3 ]

3 голосов
/ 17 февраля 2009

Одним из вариантов может быть использование заводского шаблона, чтобы объекты, созданные непосредственно контейнером IoC, никогда не нуждались в удалении самим, например,

IBinaryDataProviderFactory factory =
    ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>();
using(IBinaryDataProvider provider = factory.CreateProvider())
{
    ...
}

Недостаток - сложность, но это означает, что контейнер никогда не создает ничего, от чего должен избавиться разработчик - это всегда явный код, который делает это.

Если вы действительно хотите сделать это очевидным, фабричный метод может быть назван как-нибудь как CreateDisposableProvider ().

2 голосов
/ 17 февраля 2009

(Отказ от ответственности: я отвечаю на этот вопрос на основе Java. Хотя я программирую на C #, я ничего не проксировал в C #, но я знаю, что это возможно. Извините за терминологию Java)

Вы можете позволить каркасу IoC проверять создаваемый объект, чтобы увидеть, поддерживает ли он IDisposable. Если нет, вы можете использовать динамический прокси для обертывания фактического объекта, который инфраструктура IoC предоставляет клиентскому коду. Этот динамический прокси может реализовывать IDisposable, так что вы всегда будете предоставлять IDisposable клиенту. Пока вы работаете с интерфейсами, это должно быть довольно просто?

Тогда у вас просто будет проблема связаться с разработчиком , когда объект является IDisposable. Я не совсем уверен, как это было бы сделано хорошим способом.

1 голос
/ 27 апреля 2009

Вы на самом деле придумали очень грязное решение: ваш IService контракт нарушает SRP , что является большим нет-нет.

Я рекомендую отличать так называемые «синглтонные» сервисы от так называемых «прототипных» сервисов. Время жизни «одноэлементных» управляется контейнером, который может запросить во время выполнения, реализует ли конкретный экземпляр IDisposable и вызвать Dispose() при завершении работы, если так.

Управление прототипами, с другой стороны, является полностью ответственностью вызывающего кода.

...