问题
So I'm well underway in my effort to convert my desktop/WPF solution from using Service Locator pattern to using Dependency Injection. So far it has been relatively painless (since the same UnityContainer is used in both cases): I just remove each call to my global/static ServiceLocator and put the dependency in the constructor. But I'm stumped when it comes to a helper service that exists in one of my entity classes.
Currently I have something like this: A singleton helper service which doesn't contain any state but just some commonly-used logic:
interface ICalculationsHelper { double DoCompletelyCrazyCalculations(); }
then, its use in an domain model entity:
class CrazyNumber
{
public int Id { get; set; }
public string Name { get; set; }
public double TheNumber { get; set; }
ICalculationsHelper _helper = ServiceLocator.Resolve<ICalculationsHelper>();
public CrazyNumber()
{
CreateCrazyNumber();
}
private void CreateCrazyNumber()
{
TheNumber = _helper.DoCompletelyCrazyCalculations();
}
}
...which is no problem for Entity Framework to materialize objects, and I can use this class in many ways (e.g. wrapping in ViewModels, manipulating in lists, etc) very simply because I'm just dealing with a default constructor.
Now what happens if I this (remove ServiceLocator and put the dependent helper in the constructor):
class CrazyNumber
{
public int Id { get; set; }
public string Name { get; set; }
public double TheNumber { get; set; }
ICalculationsHelper _helper;
public CrazyNumber(ICalculationsHelper helper)
{
_helper = helper;
CreateCrazyNumber();
}
private void CreateCrazyNumber()
{
TheNumber = _helper.DoCompletelyCrazyCalculations();
}
}
1) How is EF supposed to inject a new one of these helpers for each entity? 2) Say my app manipulates the entity in 100 places and in various different ways--all with a default constructor. Now all the sudden I have to modify each of those algorithms to manually pass an ICalculationsHelper into the entity. This is major clutter and complicates each algorithm. It seems much cleaner to have this "minor" service silently loaded in the background (as per service locator pattern). 3) If it turns out that using service locator is better in this case, how is this domain class supposed to be unit tested (mocking the service)?
Thanks
回答1:
1) How is EF supposed to inject a new one of these helpers for each entity?
It's not possible to use the IoC container for this. An IoC container knows how to inject dependencies into (root) objects it creates itself, not objects created otherwise. So if you want entities to have this dependency (which is disputable, see later), you can subscribe a handler to the wrapped ObjectContext
's ObjectMaterialized event:
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized +=
ObjectContext_ObjectMaterialized;
In the handler you can check whether a CrazyNumber
was materialized and assign a ICalculationsHelper
to it. So this is not constructor injection, but property injection (sort of, not by an IoC container). Preluding on the remarks below, you could also generate its Thenumber
there without ever injecting the service.
2) ...It seems much cleaner to have this (...) service locator pattern
I agree. If IoC can't work for whatever reason, SL is second best.
3) how is this domain class supposed to be unit tested?
That's a general IoC issue. For unit tests your IoC container should be able to inject a mock service if the service itself has dependencies that can't be fulfilled in the context of unit tests
But I doubt whether you should inject this service into the entities at all. This touches a broad subject, but I'll give a thought or two here:
I especially don't like the fact that the service does its work in the constructor. The object is constructed by EF, so whatever the service does, it may interfere with object construction by EF.
Why should
CrazyNumber
create itsTheNumber
property itself (granted that your code is just a stand-in for the real case)? From an object-oriented point of view this should happen if it combines data and behavior. In other words, ifCrazyNumber
contains the state that's required to generate the number. But this state can't ever be guaranteed to be complete or stable in a constructor. (It is afterObjectMaterialized
). And if this state is not required, then the behavior shouldn't be there at all (single responsibility).Maybe your example is a bit contrived, maybe the service is needed to create the number later, e.g. when it's first accessed. Then too,
CrazyNumber
shouldn't have this dependency, because there are likely to be code paths in which the number is never generated. In that case, the service is a loose dependency and it will be hard to tell when it actually needs it. It would be better to "inject" it by method injection when it's really needed:public void CreateCrazyNumber(ICalculationsHelper helper) { TheNumber = helper.DoCompletelyCrazyCalculations(); }
来源:https://stackoverflow.com/questions/29265824/how-to-inject-helper-dependencies-in-domain-model-entity-classes