Mechanism for Dependency Injection to Provide the Most Specific Implementation of a Generic Service Interface
So I was able to roll something that did what I needed.
First I made an interface:
public interface IEntityPolicy<T>
{
string GetPolicyResult(BaseEntity entity);
}
Then I made a few implementations:
public class BaseEntityPolicy : IEntityPolicy<BaseEntity>
{
public string GetPolicyResult(BaseEntity entity) { return nameof(BaseEntityPolicy); }
}
public class GrandChildAEntityPolicy : IEntityPolicy<GrandChildAEntity>
{
public string GetPolicyResult(BaseEntity entity) { return nameof(GrandChildAEntityPolicy); }
}
public class ChildBEntityPolicy: IEntityPolicy<ChildBEntity>
{
public string GetPolicyResult(BaseEntity entity) { return nameof(ChildBEntityPolicy); }
}
I registered each of them.
// ...
.AddSingleton<IEntityPolicy<BaseEntity>, BaseEntityPolicy>()
.AddSingleton<IEntityPolicy<GrandChildAEntity>, GrandChildAEntityPolicy>()
.AddSingleton<IEntityPolicy<ChildBEntity>, ChildBEntityPolicy>()
// ...
As well as registering a policy provider class that looks something like this:
public class PolicyProvider : IPolicyProvider
{
// constructor and container injection...
public List<T> GetPolicies<T>(Type entityType)
{
var results = new List<T>();
var currentType = entityType;
var serviceInterfaceGeneric = typeof(T).GetGenericDefinition();
while(true)
{
var currentServiceInterface = serviceInterfaceGeneric.MakeGenericType(currentType);
var currentService = container.GetService(currentServiceInterface);
if(currentService != null)
{
results.Add(currentService)
}
currentType = currentType.BaseType;
if(currentType == null)
{
break;
}
}
return results;
}
}
This allows me to do the following:
var grandChild = new GrandChildAEntity();
var policyResults = policyProvider
.GetPolicies<IEntityPolicy<BaseEntity>>(grandChild.GetType())
.Select(x => x.GetPolicyResult(x));
// policyResults == { "GrandChildAEntityPolicy", "BaseEntityPolicy" }
More importantly I can do this without knowing the particular subclass.
var entities = new List<BaseEntity> {
new GrandChildAEntity(),
new BaseEntity(),
new ChildBEntity(),
new ChildAEntity() };
var policyResults = entities
.Select(entity => policyProvider
.GetPolicies<IEntityPolicy<BaseEntity>>(entity.GetType())
.Select(policy => policy.GetPolicyResult(entity)))
.ToList();
// policyResults = [
// { "GrandChildAEntityPolicy", "BaseEntityPolicy" },
// { "BaseEntityPolicy" },
// { "ChildBEntityPolicy", "BaseEntityPolicy" },
// { "BaseEntityPolicy" }
// ];
I expanded on this a bit to allow the policies to provide an ordinal value if necessary and added some caching inside GetPolicies
so it doesn't have to construct the collection every time. I've also added some logic which allows me to define interface policies IUnusualEntityPolicy : IEntityPolicy<IUnusualEntity>
and pick those up as well. (Hint: Subtract the interfaces of currentType.BaseType
from currentType
to avoid duplication.)
(It's worth mentioning that the order of List
is not guaranteed so I have used something else in my own solution. Consider doing the same before using this.)
Still not sure if this is something that already exists or if there's a term for it but it makes managing entity policies feel decoupled in a way that's manageable. For example if I registered a ChildAEntityPolicy : IEntityPolicy<ChildAEntity>
my results would automatically become:
// policyResults = [
// { "GrandChildAEntityPolicy", "ChildAEntityPolicy", "BaseEntityPolicy" },
// { "BaseEntityPolicy" },
// { "ChildBEntityPolicy", "BaseEntityPolicy" },
// { "ChildAEntityPolicy", "BaseEntityPolicy" }
// ];
EDIT: Though I haven't yet tried it, @xander's answer below seems to illustrate that Simple Injector can provide much of the behavior of the PolicyProvider
"out of the box". There's still a slight amount of Service Locator
to it but considerably less so. I'd highly recommend checking that out before using my half-baked approach. :)
EDIT 2: My understanding of the dangers around a service locator is that it makes your dependencies a mystery. However these policies are not dependencies, they're optional add-ons and the code should run whether or not they've been registered. With regard to testing, this design separates the logic to interpret the sum results of the policies and the logic of the policies themselves.
First thing that strikes me as odd is that you define
interface IEntityService<T> where T : BaseEntity { void DoSomething(BaseEntity entity)... }
instead of
interface IEntityService<T> where T : BaseEntity { void DoSomething(T entity)... }
while you still provide different implementations for each T
.
In a well designed hierarchy DoSomething(BaseEntity entity)
shouldn't have to change its functionality based on the actual (derived) type.
If this is the case, you could extract the functionality following the Interface segregation principle.
If the functionality really is that subtype dependent, perhaps the DoSomething()
interface belongs on the types themselves.
If you want to change algorithms at runtime there's also the Strategy pattern, but even then the concrete implementations aren't meant to be changed that often (i.e. while iterating a list).
Without more information about your design and what you're trying to accomplish, it's hard to provide further guidance. Please ref:
- Liskov substitution principle
- Interface segregation principle
- Strategy pattern
Do note Service Locator is considered an anti-pattern. A DI container's sole purpose should be to compose the object graph at startup (in composition root).
As for a good read, if you like to cook, there's Dependency Injection in .NET (Manning pub, 2nd ed coming out).
UPDATE
I don't want to change algorithms at runtime in my use case. But I do want it to be easy to swap out segments of business logic without touching the classes they operate on.
That's what DI is all about. Instead of creating services to manage all your business logic - which results in an Anemic Domain Model and seems to have generic variance working against you - it pays to abstract your volatile dependencies - those likely to change - behind and interface, and inject those into your classes.
The example below uses constructor injection.
public interface ISleep { void Sleep(); }
class Nocturnal : ISleep { public void Sleep() => Console.WriteLine("NightOwl"); }
class Hibernate : ISleep { public void Sleep() => Console.WriteLine("GrizzlyBear"); }
public abstract class Animal
{
private readonly ISleep _sleepPattern;
public Animal(ISleep sleepPattern)
{
_sleepPattern = sleepPattern ?? throw new NullReferenceException("Can't sleep");
}
public void Sleep() => _sleepPattern.Sleep();
}
public class Lion : Animal
{
public Lion(ISleep sleepPattern)
: base(sleepPattern) { }
}
public class Cat : Lion
{
public Cat(ISleep sleepPattern)
: base(sleepPattern) { }
}
public class Bear : Animal
{
public Bear(ISleep sleepPattern)
: base(sleepPattern) { }
}
public class Program
{
public static void Main()
{
var nocturnal = new Nocturnal();
var hibernate = new Hibernate();
var animals = new List<Animal>
{
new Lion(nocturnal),
new Cat(nocturnal),
new Bear(hibernate)
};
var Garfield = new Cat(hibernate);
animals.Add(Garfield);
animals.ForEach(a => a.Sleep());
}
}
Of course, we've barely scratched the surface, but it's invaluable for building maintainable "plug and play" solutions. Though it takes a mind shift, explicitly defining your dependencies will improve your code base in the long run. It allows you to recompose your dependencies when you start to analyze them, and by doing so you can even gain domain knowledge.
UPDATE 2
In your sleep example how would
new Bear(hibernate)
andnew Lion(nocturnal)
be accomplished using a DI Container?
The abstractions make the code flexible for change. They introducing seams in the object graph, so you can easily implement other functionality later on. At startup, the DI Container is populated and asked to build the object graph. At that time, the code is compiled, so there's no harm in specifying concrete classes if the backing abstraction is too vague. In our case, we want to specify the ctor argument. Remember, the seams are there, at this time we're merely constructing the graph.
Instead of auto wiring
container.Register(
typeof(IZoo),
typeof(Zoo));
We can do it by hand
container.Register(
typeof(Bear),
() => new Bear(hibernate));
Note the ambiguity comes from the fact that there are multiple ISleep sleepPattern
s in play, so we need to specify one way or another.
How do I provide IHunt in Bear.Hunt and Cat.Hunt but not Lion.Hunt?
Inheritance will never be the most flexible of options. That's why composition is often favored, not to say you should drop every hierarchy, but be aware of friction along the way. In the book I mentioned there's an entire chapter on interception, it explains how to use the decorator pattern to dynamically decorate an abstraction with new capabilities.
In the end, the I want the container to choose the closest match in the hierarchy approach just doesn't sound right to me. Though it might seem convenient, I'd prefer to set the container up right.