Reputation: 2989
I currently have a static OrderEvaluatorFactory
which is used to create various kinds of Order Evaluators.
A new kind Order Evaluator should now use an order processor which is only available through Dependency Injection.
The factory class currently looks like this. It makes use of an attribute to search the desired type for instanciation and then uses System.Activator.CreateInstance()
to create an instance.
public static class OrderEvaluatorFactory
{
private static Dictionary<Type, Type> orderEvaluatorTypesByOrderTypes = null;
public static IOrderEvaluator CreateFor(OrderType ot)
{
if (orderEvaluatorTypesByOrderTypes == null)
{
orderEvaluatorTypesByOrderTypes = Utilities.TypeFinder<OrderTypeEvaluatorForAttribute, Type>.GetTypesByAttribute(
typeof(OrderEvaluatorFactory).Assembly, type => typeof(IOrderEvaluator).IsAssignableFrom(type),
attribute => attribute.For);
}
Type orderEvaluatorType = evaluatorTypesByOrderTypes[ot.GetType()];
if (evaluatorType == null)
throw new Exception("No evaluator type found for " + ot.GetType());
return (IOrderEvaluator)Activator.CreateInstance(evaluatorType);
}
}
And this is how the evaluator looks, which should make use of dependency injection:
[OrderTypeEvaluatorFor(typeof(SuperFastDeliveryOrder))]
public class SuperFastDeliveryOrderEvaluator : IOrderEvaluator
{
private readonly IOrderProcessor orderProcessor;
public SuperFastDeliveryOrderEvaluator(IOrderProcessor orderProcessor)
{
this.orderProcessor = orderProcessor;
}
public bool Eval(Customer cstm)
{
// omitted...
return true;
}
}
This is the only evaluator that (currently) needs IOrderProcessor
. So I don't want to pass the object to simply to all other types as well (e.g. as parameter).
The problem I am now faced with is that my constructor (with IOrderProcessor
) is not getting called (as I believe Activator.CreateInstance()
only calls the default constructor) and as said I don't want to use an overload to pass an IOrderProcessor
to every object because not all object need one.
I guess I have now a bit a mess, because the factory pattern (especially a static factory) does not go well with DI. Unfortunately I don't see a nice way to solve this issue.
I guess I could make the factory non-static and add it to my DI container, allowing me to pass IOrderProcessor
to this factory but the Activator would still remain.
But how could I replace the Activator with something more suited for DI that doesn't require me to hardcode the instanciation for every currently known Evaluator type?
Upvotes: 1
Views: 680
Reputation: 23894
If you have control over it, then the Activator
doesn't actually have to remain. Instead, use the factory as a wrapper around DI proper.
Given the question is currently tagged autofac
(though the question itself doesn't mention Autofac) I'll assume you're using Autofac.
First, update your factory to be an instance class so it can be provided to whatever needs it. In the constructor, take an ILifetimeScope
. Autofac will automatically inject the lifetime scope from which the factory was resolved, so if you register it as a singleton, the parameter will be the root lifetime scope; if you register it as InstancePerDependency
(the default) then if your web app has a request lifetime scope, that will be what's injected.
Next, use the scope to resolve the thing you want instead of using Activator
.
public class OrderEvaluatorFactory
{
private static Dictionary<Type, Type> orderEvaluatorTypesByOrderTypes = null;
private readonly ILifetimeScope _scope;
public OrderEvaluatorFactory(ILifetimeScope scope)
{
// Take in the lifetime scope that owns the factory.
this._scope = scope;
}
public IOrderEvaluator CreateFor(OrderType ot)
{
if (orderEvaluatorTypesByOrderTypes == null)
{
orderEvaluatorTypesByOrderTypes = Utilities.TypeFinder<OrderTypeEvaluatorForAttribute, Type>.GetTypesByAttribute(
typeof(OrderEvaluatorFactory).Assembly, type => typeof(IOrderEvaluator).IsAssignableFrom(type),
attribute => attribute.For);
}
Type orderEvaluatorType = evaluatorTypesByOrderTypes[ot.GetType()];
if (evaluatorType == null)
throw new Exception("No evaluator type found for " + ot.GetType());
// Resolve the evaluator from the scope.
return (IOrderEvaluator)this._scope.Resolve(evaluatorType);
}
}
The key to controlling where the evaluators come from is
What you choose here is entirely app dependent so I can't give you a "recommendation" or "best practice." If these things are expensive to create or if they don't maintain state, maybe registering them all as singletons would be best. If they use state and are not disposable maybe the factory is a singleton but the evaluators aren't. (If they're disposable and they're resolved from the root lifetime scope then you can run into a memory leak because scopes will hold disposable components until the scope itself gets disposed.)
Upvotes: 2