We recently had an issue in C# when working with DI containers and I wanted a clean solution, but I didn't find any resources for my approach.
We are using DryIoc heavily and whenever we need a collection of items, there is a Repository or Registry or whatever to keep the items in one place.
This approach has some downsides:
A developer has to know where he has to register it's class.
The registry and its purpose have to be documented
Some objects have to be resolved by another object (Object A relies on Object B) as a feature, so almost duplicate registries or repositories and even more dependencies.
My approach is to create generic interfaces like this:
interface IBeverage { }
interface IBeverage<out T> : IBeverage where T : IBeverage{ }
This is quite equal to CRTP, but slightly different as I have a base interface and work solely with Interfaces. The advantage is that I have a strong hierarchy, but can use this to express Features or Meta Information without inheritance:
interface ICaffeine { }
interface ICaffeine<out T> : ICaffeine where T : ICaffeine{ }
Both a coffee and an energy drink are implementing both interfaces, while water only implements the first one:
class Coffee : IBeverage<Coffee>, ICaffeine<Coffee> { }
class EnergyDrink : IBeverage<EnergyDrink>, ICaffeine<EnergyDrink> { }
class Water : IBeverage<Water> { }
class CaffeineTablet : ICaffeine<CaffeineTablet> { }
When I now want to resolve all beverages, I can resolve with
container.Resolve<IEnumerable<IBeverage>>();
Or all caffeine:
container.Resolve<IEnumerable<ICaffeine>>();
Now, I've changed the Interfaces a little bit more to:
interface IBeverage { }
interface IBeverage<out T> : IBeverage { }
interface ICaffeine { }
interface ICaffeine<out T> : ICaffeine { }
Because both interfaces are simply features and can now request them from DryIoc like this:
//Returns coffee, energy drink, and water
var allBeverages = container.Resolve<IEnumerable<IBeverage>>();
//Returns coffee, energy drink, and caffeine tablet
var allCaffeine = container.Resolve<IEnumerable<ICaffeine>>();
//Returns coffee and energy drink
var allBeveragesWithCaffeine = container.Resolve<IEnumerable<IBeverage<ICaffeine>>>();
I could even add a third feature or add a feature like HotBeverage : Beverage and it still works like a charm.
I was stunned after I found out about this feature, but how is my pattern called? I really would like to find more information about it.