I am starting to apply SOLID principles, and am finding them slightly contradictory. My issue is as follows:
My understanding of dependency inversion principle is that classes should depend on abstractions. In practice this means classes should be derived from interfaces. All fine so far.
Next my understanding of the open/closed principle is that after a certain cut off point, you should not alter the contents of a class, but should extend and override. This makes sense so far to me.
So given the above, I would end up with something like this:
public interface IAbstraction
{
string method1(int example);
}
public Class Abstraction : IAbstraction
{
public virtual string method1(int example)
{
return example.toString();
}
}
and then at time T, method1 now needs to add " ExtraInfo" onto its returned value. Rather than altering the current implementation, I would create a new class that extends Abstraction
and make it do what I needed, as follows.
public Class AbstractionV2 : Abstraction
{
public override string method1(int example)
{
return example.toString() + " ExtraInfo";
}
}
And I can see the reason for doing this is that only the code I want to call this updated method will call it, and the rest of the code will call the old method.
All makes sense to me - and I assume my understanding is correct??
However, I am also using dependency injection (simple injector), so my implementations are never through a concrete class, but instead are through my DI configuration, as follows:
container.Register<IAbstraction, Abstraction>();
The issue here is that under this setup, I can either update my DI config to be:
container.Register<IAbstraction, AbstractionV2>();
In which case all instance will now call the new method, meaning I have failed to achieve not changing the original method.
OR
I create a new interface IAbstractionV2
and implement the updated functionality there - meaning duplication of the interface declaration.
I cannot see any way around this - which leads me to wonder if dependency injection and SOLID are compatible? Or am I missing something here?
Addressing the exact problem you mentioned:
You have classes that depend on IAbstraction
and you've registered an implementation with the container:
container.Register<IAbstraction, Abstraction>();
But you're concerned that if you change it to this:
container.Register<IAbstraction, AbstractionV2>();
then every class that depends on IAbstraction
will get AbstractionV2
.
You shouldn't need to choose one or the other. Most DI containers provide ways that you can register more than one implementation for the same interface, and then specify which classes get which implementations. In your scenario where only one class needs the new implementation of IAbstraction
you might make the existing implementation the default, and then just specify that one particular class gets a different implementation.
I couldn't find an easy way to do this with SimpleInjector. Here's an example using Windsor:
var container = new WindsorContainer();
container.Register(
Component.For<ISaysHello, SaysHelloInSpanish>().IsDefault(),
Component.For<ISaysHello, SaysHelloInEnglish>().Named("English"),
Component.For<ISaysSomething, SaysSomething>()
.DependsOn(Dependency.OnComponent(typeof(ISaysHello),"English")));
Every class that depends on ISaysHello
will get SaysHelloInSpanish
except for SaysSomething
. That one class gets SaysHelloInEnglish
.
UPDATE:
The Simple Injector equivalent is the following:
var container = new Container();
container.Register<ISaysSomething, SaysSomething>();
container.RegisterConditional<ISayHello, SaysHelloInEnglish>(
c => c.Consumer.ImplementationType == typeof(SaysSomething));
container.RegisterConditional<ISayHello, SaysHelloInSpanish>(
c => c.Consumer.ImplementationType != typeof(SaysSomething))