Everything works fine till the constructors of the realizations are the same. However, I have a sort of dilemma when the constructors are different. Is it okay or maybe there is an architecture issue? Here is the code:
public class CategoryViewFactory : ICategoryViewFactory
{
private readonly ActiveProgressions _activeProgressions;
public CategoryViewFactory(ActiveProgressions activeProgressions)
{
_activeProgressions = activeProgressions;
}
public ICategoryView? Create(CategoryType type, Category category)
{
return type switch
{
CategoryType.Category => new CategoryView(category),
CategoryType.Subcategory => new SubcategoryView(category, _activeProgressions),
_ => null
};
}
}
ActiveProgression
is a singleton service which I inject using the container. Is it okay? And what I should do if, for instance, ActiveProgression
is a transient service? How can I create SubcategoryView
in this case?
The idea of putting extra parameters in Create()
seems quite bad, but it also seems the only solution in this case.
CodePudding user response:
CategoryViewFactory
's Create
method acts as a Composer. A Composer is part of your application's Composition Root that is responsible for composing instances. In this case the Create
method is using Pure DI rather can moving that responsibility to a DI Container.
As long as CategoryViewFactory
is defined in the application's startup path, a.k.a. the Composition Root, everything what you're doing is perfectly fine. It's even fine for ActiveProgression
to call back into the DI Container, *as long as ActiveProgression
is part of the Composition Root as well. When ActiveProgression
is defined outside the context of the Composition Root, it's use of the DI Container can be considered a Service Locator, which is a well-known anti-pattern.
And what I should do if, for instance, ActiveProgression is a transient service?
As long as CategoryViewFactory
is transient as well, there will be no problem. When CategoryViewFactory
is singleton, however, ActiveProgression
will become a Captive Dependency.
Although, what you're doing is perfectly fine, it might be less obvious for CategoryViewFactory
to use Pure DI, while other parts of your Composition root use a DI Container. You can consider CategoryViewFactory
to forward the responsibility of composition to the DI Container. For instance:
public class CategoryViewFactory : ICategoryViewFactory
{
private readonly MyDiContainer container;
public CategoryViewFactory(MyDiContainer container)
{
this.container = container;
}
public ICategoryView? Create(CategoryType type, Category category)
{
var view = this.CreateInternal(type);
view?.Initialize(category);
return view;
}
private ICategoryView? CreateInternal(CategoryType type)
{
return type switch
{
CategoryType.Category => container.GetService<CategoryView>(),
CategoryType.Subcategory => container.GetService<SubcategoryView>(),
_ => null
};
}
}
Notice how in this case, the Category
runtime data is not supplied any longer to the constructor of the created views. This simplifies the creation of those objects, because their construction no longer depends on the existence of runtime data, which is a good practice. This also requires changing your ICategoryView
.