Я предполагаю, что этот фабричный класс должен существовать внутри моего корня композиции, потому что он ссылается на определенный контейнер DI.
Абсолютно. Только Composition Root должен относиться к контейнеру. Обычно это означает, что вам нужно ввести абстракцию. Это позволяет реализовать логику адаптера, которая зависит от контейнера DI внутри корня композиции, а код приложения может зависеть от его абстракции.
Но для меня корень композиции предназначен только для составления графов объектов, и, как правило, в нем нет логики, которая использует эти объекты, как в приведенном выше коде.
Не следует размещать бизнес-логику внутри корня композиции. Корень композиции является частью инфраструктуры приложения. Это не значит, однако, что он может только составлять графы объектов. Разрешено вызывать созданный граф объектов. Было бы довольно сложно сделать что-нибудь полезное, если бы не разрешалось воздействовать на составной граф объектов.
Как я могу создать набор объектов в одном месте и использовать их в другом, а затем уничтожить их, когда их работа будет выполнена?
Чтобы решить эту проблему, вы должны попытаться подойти к этому под другим углом. Ваш корень композиции в идеале должен иметь только один вызов GetInstance
и один вызов метода для разрешенных объектов. Как правило, этого можно достичь, поместив весь код в новый класс. Разрешенные зависимости будут преобразованы в аргументы конструктора в новом классе.
Когда вы применяете эту технику к своему коду, вы получите что-то вроде этого:
public class CallExecutor {
...
public CallExecutor(
SocketDestinationProvider socketDestinationProvider, Socket socket,
MessageSender1 messageSender1, MessageSender2 messageSender2,
ClassThatChecksSomethingOnATimer checker)
{
this.socketDestinationProvider = socketDestinationProvider;
this.socket = socket;
this.messageSender1 = messageSender1;
this.messageSender2 = messageSender2;
this.checker = checker;
}
public async Task Call()
{
EndPoint ep = await this.socketDestProvider.GetSocketDestination();
await this.socket.ConnectAsync(ep);
await this.sender1.SendStartMessage();
var response = await this.sSender2.SendStartMessageAndAwaitResponse();
if (response.Result)
{
this.checker.DoSomething(...)
}
TaskCompletionSource<int> Completion = new TaskCompletionSource<int>();
socket.Closed += (sender, e) => { Completion.TrySetResult(0); };
await Completion.Task;
await this.sender2.SendStopMessage();
await this.sender1.SendStopMessage();
await this.socket.DisconnectAsync();
}
}
В приведенном выше коде я сделал простое преобразование один в один. Каждая решимость становилась зависимостью конструктора. Это может быть не правильно во всех случаях. Например, я могу представить, что вы хотите разрешить несколько Socket
экземпляров из контейнера. Заметьте, однако, что Socket
мне больше походит на данные времени выполнения. Возможно, вы захотите воздержаться от использования Контейнера для разрешения подобных объектов (как указано в этой статье ).
Этот новый класс CallExecutor
может быть полностью определен как код приложения; нет никакой зависимости от DI-контейнера. Оставшийся код CallTaskFactory
настолько короткий, что его легко реализовать в корне композиции:
class CallTaskFactory : ICallTaskFactory
{
private Container container;
public CallTaskFactory(Container container)
{
this.container = container;
}
public async Task CreateCallTask()
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
await this.container.GetInstance<CallExecutor>().Call();
}
}
}
Конечно, введение CallExecutor
, однако, привело к стремительному созданию всех зависимостей. Это может показаться неэффективным, но не должно быть так, потому что композиция объектов должна быть быстрой , поэтому вы можете составлять графики с уверенностью . Когда вы сделаете это в сочетании с Simple Injector, вы вряд ли когда-либо столкнетесь с проблемами производительности, поскольку Simple Injector может легко создать тысячи объектов за долю секунды.