I feel like I played buzzword bingo with the title. Here's a concise example of what I'm asking. Let's say I have some inheritance hierarchy for some entities.
class BaseEntity { ... }
class ChildAEntity : BaseEntity { ... }
class GrandChildAEntity : ChildAEntity { ... }
class ChildBEntity : BaseEntity { ... }
Now let's say I have a generic interface for a service with a method that uses the base class:
interface IEntityService<T> where T : BaseEntity { void DoSomething(BaseEntity entity)... }
I have some concrete implementations:
class BaseEntityService : IEntityService<BaseEntity> { ... }
class GrandChildAEntityService : IEntityService<GrandChildAEntity> { ... }
class ChildBEntityService : IEntityService<ChildBEntity> { ... }
Assume I've registered these all with the container. So now my question is if I'm iterating through a List
of BaseEntity
how do I get the registered service with the closest match?
var entities = List<BaseEntity>();
// ...
foreach(var entity in entities)
{
// Get the most specific service?
var service = GetService(entity.GetType()); // Maybe?
service.DoSomething(entity);
}
What I'd like to do is have a mechanism set up such that if an entity has a type of ClassA
the method would find no service for the specific class and so would return BaseEntityService
. Later if someone came along and added a registration for this service:
class ClassAEntityService : IEntityService<ChildAEntity> { ... }
The hypothetical GetService
method would start providing the ClassAEntityService
for the ClassA
types without requiring any further code changes. Conversely if someone came along and just removed all the services except BaseEntityService
then the GetService
method would return that for all classes inheriting from BaseEntity
.
I'm pretty sure I could roll something even if the DI container I'm using doesn't directly support it. Am I falling into a trap here? Is this an anti pattern?
EDIT:
Some discussion with @Funk (see below) and some additional Google searches those discussions made me think to look up has made me add some more buzzwords to this. It seems like I'm trying collect all the advantages of DI Containers, the Strategy Pattern and the Decorator Pattern in a type safe way and without using a Service Locator Pattern. I'm beginning wonder if the answer is "Use a Functional Language."
First thing that strikes me as odd is that you define
instead of
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:
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
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.
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
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
We can do it by hand
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.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.
With Simple Injector
If you happen to be using Simple Injector for DI duties, the container can help with this. (If you're not using Simple Injector, see "With Other DI Frameworks," below)
The functionality is described in the Simple Injector docs, under Advanced Scenarios: Mixing collections of open-generic and non-generic components.
You'll need to make a slight adjustment to your service interface and implementations.
The services are now generic, with a type constraint describing the least specific entity type they're able to handle. As a bonus,
DoSomething
now adheres to the Liskov Substitution Principle. Since the service implementations provide type constraints, theIEntityService
interface no longer needs one.Register all of the services as a single collection of open generics. Simple Injector understands the generic type constraints. When resolving, the container will, essentially, filter the collection down to only those services for which the type constraint is satisfied.
Here's a working example, presented as an xUnit test.
Similar to your example, you can add
ChildAEntityService<T> : IEntityService<T> where T : ChildAEntity
andUnusualEntityService<T> : IEntityService<T> where T : IUnusualEntity
and everything works out...As I mentioned before, this example is specific to Simple Injector. Not all containers are able to handle generic registrations so elegantly. For example, a similar registration fails with Microsoft's DI container:
With Other DI Frameworks
I've devised an alternate solution that should work with any DI container.
This time, we remove the generic type definition from the interface. Instead, the
CanHandle()
method will let the caller know whether an instance can handle a given entity.An abstract base class can handle most of the type-checking/casting boilerplate:
Which means the actual service implementations can be very simple, like:
To get them out of the DI container, you'll want a friendly factory:
And finally, to prove it all works:
Because of the casting involved, I don't think this is as elegant as the Simple Injector implementation. It's still pretty good, though, and the pattern has some precedent. It's very similar to the implementation of MVC Core's Policy-Based Authorization; specifically
AuthorizationHandler
.So I was able to roll something that did what I needed.
First I made an interface:
Then I made a few implementations:
I registered each of them.
As well as registering a policy provider class that looks something like this:
This allows me to do the following:
More importantly I can do this without knowing the particular subclass.
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 policiesIUnusualEntityPolicy : IEntityPolicy<IUnusualEntity>
and pick those up as well. (Hint: Subtract the interfaces ofcurrentType.BaseType
fromcurrentType
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: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 ofService 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.