Я действительно ценю ответы всех остальных. Они помогут мне решить эту проблему. Скорее всего, я приму ответ Ремо, поскольку он соответствует текущей проблеме, с которой я на самом деле сталкиваюсь.
Насколько я понимаю, я также хотел бы получить ответ на этот более широкий ответ, который я придумал.
Я не был уверен, что Dependency Injection напрямую поддерживает механизмы, о которых я говорил, через конструктор, свойство или внедрение метода. Это то, что я бы назвал «чистым» DI на данный момент - хотя я готов к тому, чтобы его покачали.
Я думал, введение зависимостей означало относительно статический граф объектов. Он может быть загружен из файла конфигурации или сгенерирован процедурно, но он не может напрямую приспособить непознаваемое состояние времени выполнения, как пользователь, неоднократно запрашивающий новые экземпляры.
Однако после обдумывания некоторых из этих альтернатив я начинаю думать, что существуют обходные пути, поддерживающие чистоту, и что, возможно, описанная мною чистота не так важна, как я думал. Некоторые из менее «чистых» опций по-прежнему работают с большинством контейнеров в основном чисто, и, кажется, достаточно легко добавить поддержку в контейнер, чтобы очистить их до конца.
Вот обходные пути, которые я рассмотрел до сих пор (некоторые из них уже упоминались).
Ссылка на контейнер на пользовательских фабриках, а затем мыть руки:
Ваши компоненты могут быть реализованы так, как вы хотите. Вы можете использовать любой контейнер, который хотите (если он поддерживает временные экземпляры). Вам просто нужно смириться с тем фактом, что вы будете внедрять фабрики в ваш код, , и что эти фабрики будут напрямую обрабатываться из контейнера . Кому нужна чистота, когда вы можете быть прагматичным?
Пример кода:
public class ComponentFactory // Might inherit from an interface...
{
private readonly IContainer container;
public ComponentFactory(IContainer container)
{
this.container = container;
}
public IComponent Create(IOtherComponent otherComponent)
{
return container.Get<IComponent>(otherComponent);
}
}
Используйте фабричное расширение для конкретного контейнера:
Ваши компоненты могут быть реализованы так, как вы хотите. Но ваш контейнер должен напрямую поддерживать внедрение фабрик в ваш код и автоматическую реализацию этих фабрик, чтобы у них не было специальных знаний о контейнере.
Пример кода:
// Black magic - the container implemented it for us!
// But the container basically implemented our code from the previous example...
public interface IComponentFactory
{
public IComponent Create(IOtherComponent otherComponent);
}
Использовать пул объектов для конкретного контейнера:
Убедитесь, что ваши компоненты не имеют состояния, и разрешите их объединение. Контейнер позаботится о выделении или объединении объектов для вас. Это более или менее управляемая фабрика с причудливой реализацией, которую нужно как выделять, так и освобождать.
Псевдокод (ранее я не использовал пул объектов на основе conatiner):
public class SomeUI
{
private readonly IComponentPool componentPool;
public OtherComponent(IComponentPool componentPool)
{
this.componentPool = componentPool;
}
public void DoSomethingWhenButtonPushed()
{
var component = componentPool.Get();
component.DoSomething();
componentPool.Release(component);
}
}
Преимущество этого псевдокода в том, что вам не нужно было определять интерфейс для вашей фабрики. Недостатком является то, что вы должны зависеть от интерфейса пула, поэтому у вашего контейнера есть свои усики. Кроме того, я не смог ничего передать методу Get
. Это, вероятно, имеет смысл, поскольку объекты должны поддерживать повторное использование экземпляра.
Если реальные пулы не работают таким образом, они могут выглядеть идентично приведенному выше примеру «фабричного расширения контейнера», только для них всегда требуется метод Release
вместе с методом Create
.
Использовать шаблон Flyweight:
( Шаблон Flyweight - Не уверен, правильно ли я определила модель, или просто странно ее использую)
Внедрите компонент без состояния, который действует как поведение для ваших объектов, или компонент «большого веса». Поддержка отдельных «экземпляров» компонента с использованием объектов состояния flyweight, которые вы передаете компоненту поведения, или заставьте их обернуть компонент поведения.
Это сильно повлияет на архитектуру ваших компонентов. Ваши реализации должны быть без состояний, а ваш объект состояния должен быть спроектирован так, чтобы он работал для всех возможных реализаций компонентов. Но он полностью поддерживает «чистую» модель внедрения (только внедрение в конструкторы, свойства, методы).
Это не будет хорошо работать для интерфейсов.Классы представлений обычно должны создаваться напрямую, и мы не хотим, чтобы наш "flyweight" был классом пользовательского интерфейса ...
public class ComponentState
{
// Hopefully can be less generic than this...
public Dictionary<string, object> Data { get; set; }
}
public interface IComponent
{
int DoSomething(ComponentState state);
}
public SomeUI
{
private readonly IComponent component;
public OtherComponent(IComponent component)
{
this.component = component;
}
public void DoSomethingWhenButtonPushed()
{
var state = new ComponentState();
component.DoSomething(state);
}
}
Использовать дочерние контейнеры для каждого нового экземпляра, который запрашивает пользователь:
Контейнер лучше всего работает при создании графа объектов из одного корня.Вместо того, чтобы пытаться бороться с этим, работайте с этим.Когда пользователь нажимает кнопку, чтобы создать новый экземпляр вашего алгоритма, создайте новый дочерний контейнер для этих объектов и вызовите глобальный код конфигурации, однако это необходимо сделать.Затем присоедините дочерний контейнер к родительскому контейнеру.
Это означает, что код вызова должен знать о контейнере на некотором уровне.Может быть, лучше всего обернуть его на заводе.
public class SubComponentFactory // Might inherit from an interface...
{
private readonly IContainer container;
public ComponentFactory(IContainer container)
{
this.container = container;
}
public IComponent Create(IOtherComponent otherComponent)
{
// Todo: Figure out any lifecycle issues with this.
// I assume the child containers get disposed with the parent container...
var childContainer = container.CreateChildContainer();
childContainer.Configure(new SubComponentConfiguration());
return childContainer.Get<SubComponent>(otherComponent);
}
}
Вид выглядит так, как мы начали.Но у нас есть новый корневой объектный граф, поэтому я не уверен, что смогу это использовать для использования шаблона Service Locator.Проблема с этим подходом наиболее тесно связана с контейнером.Мало того, что на контейнер ссылаются напрямую, фабрика опирается на детали реализации контейнера, поддерживающего дочерние контейнеры.