Это беспокоило меня в течение долгого времени, и я не мог найти правильный ответ.
Проблема.
Представьте, что у вас есть фабричный интерфейс (пример C#):
interface IFooFactory
{
IFoo Create();
}
и его реализация зависит от службы:
class FooFactory : IFooFactory
{
private readonly IBarService _barService;
public FooFactory(IBarService barService)
{
_barService = barService;
}
}
где интерфейс службы реализует IDisposable
для корректного завершения работы:
interface IBarService : IDisposable
{
...
}
Теперь реальный класс, созданный фабрикой, имеет 2 зависимости — сам сервис (прошедший через фабрику) и еще один объект, созданный фабрикой:
class Foo : IFoo
{
public Foo(IBarService barService, IQux qux)
{
...
}
}
и фабрика может создать его так:
class FooFactory : IFooFactory
{
public IFoo Create()
{
IQux qux = new Qux();
return new Foo(_barService, qux);
}
}
Наконец, IFoo
и IQux
также реализуют IDisposable
, поэтому класс Foo
реализует:
class Foo : IFoo
{
public void Dispose()
{
_qux.Dispose();
}
}
Но почему мы размещаем Qux
только на Foo.Dispose()
? Обе зависимости внедряются, и мы просто полагаемся на знание точной реализации фабрики, где Bar
— это общая служба (тип отношения ассоциации), а Qux
используется исключительно Foo
(тип отношения композиции). Можно легко избавиться от них обоих по ошибке. И в обоих случаях Foo
логически не владеет ни одной из зависимостей, поэтому удаление любой из них кажется неправильным. Помещение создания Qux
внутри Foo
сведет на нет внедрение зависимостей, так что это не вариант.
Есть ли лучший способ иметь обе зависимости и прояснить, какие отношения у них есть, чтобы правильно справляться со своей жизнью?
Возможное решение.
Итак, вот одно из возможных не очень красивых решений:
class FooFactory : IFooFactory
{
private readonly IBarService _barService;
public FooFactory(IBarService barService)
{
_barService = barService;
}
public IFoo Create()
{
// This lambda can capture and use any input argument.
// Also creation can be complex and involve IO.
var quxFactory = () => new Qux();
return new Foo(_barService, quxFactory);
}
}
class Foo : IFoo
{
public Foo(IBarService barService, Func<IQux> quxFactory)
{
// Injected - don't own.
_barService = barService;
// Foo creates - Foo owns.
_qux = quxFactory();
}
public void Dispose()
{
// Now it's clear what Foo owns from the code in the constructor.
_qux.Dispose();
}
}
Я не сторонник вызова возможно сложной логики в конструкторе, особенно если это async
, и вызов его по запросу (ленивая загрузка) также может привести к неожиданным ошибкам позднего выполнения (по сравнению с быстрым сбоем).
Есть ли смысл заходить так далеко только ради дизайна? В любом случае я хотел бы посмотреть, есть ли другое возможное элегантное решение для этого.