This is the first part of my Back to Basics series.
One of the basics I feel we really need to get back to is the use and understanding of the value of interfaces.
In languages like C# and Java, interfaces are extremely common. They are much more commonly used than they were 5-10 years ago.
But a question we have to ask ourselves is, “are we using them correctly?”
What problem does the interface solve?
I want you to take a second and clear your head of how you are currently using interfaces.
I want you to pretend for a moment that you don’t know what an interface is.
The basic problem an interface is trying to solve is to separate how we use something from how it is implemented.
Why do we want to separate the use from the implementation?
So that we can write code that can work with a variety of different implementations of some set of responsibilities without having to specifically handle each implementation.
To put this more simply, this means that if we have a Driver class it should be able to have a method Drive that can be used to drive any car, boat, or other kind of class that implements the IDriveable interface.
The Driver class should not have to have a DriveBoat, DriveCar or DriveX methods for each kind of class that supports the same basic operations that are needed for it to be driven.
Interfaces are trying to solve a very specific problem by allowing us to interact with objects based on what they do, not how they do it.
Interfaces are contracts
Interfaces allow us to specify that a particular class meets certain expectations that other classes can rely on.
If we have a class that implements an interface, we can be sure that it will support all the methods that are defined in that interface.
At first glance interfaces seem to be similar to concrete inheritance, but there is a key difference.
Concrete inheritance says Car is an Automobile, while an interface says Car implements the Drivable interface.
When a class implements an interface, it does not mean that class IS that interface. For this reason interfaces that completely describe the functionality of a class are usually wrong.
A class can implement multiple interfaces because each interface only talks about a particular contract that class is able to fulfill.
Interfaces are always implemented by more than one class
You might be saying “no they’re not, I have a class here that has an interface that no other class implements.”
To that I say, “you are doing it wrong.”
But, don’t worry, you are not alone. I am doing it wrong also. Many of us are not using interfaces correctly anymore, but are using them instead because we are under the impression that we should never use a concrete class directly.
We are afraid of tightly coupling our application, so instead we are creating interfaces for every class whether or not we need an interface.
There are some really good reasons why I say that interfaces are always implemented by more than one class.
Remember how we talked about how interfaces are designed to solve a particular problem?
In my example, I talked about how the Driver class shouldn’t have to have a method of each kind of class it can drive, instead it should depend on an IDriveable interface and have one generic Drive method that can drive anything that implements IDrivable.
Most of us accept the YAGNI principle which says “You Ain’t Gonna Need It.” If we only have a Car class and we don’t have any other classes that need to be driven by the Driver class, we don’t need an interface. YAGNI!
At some point we may later add a Boat class. Only at that point in time do we actually have a problem that the interface will solve. Up until that point adding the interface is anticipating a future problem to solve.
If you think you are good at anticipating when you will need an interface, I want you to do a little exercise. Go into your codebase and count all the interfaces you have. Then count all the classes that implement those interfaces. I bet the ratio is pretty close to 1 to 1.
But how will I test? How will I use dependency injection?
These two reasons are probably the most justified causes for incorrectly using interfaces.
I am guilty of justifying the creation of an interface so that I can have something to mock, and I am guilty of creating an interface just for my dependency injection framework, but it doesn’t make it right.
I can’t give you an easy answer here and say that I can solve your unit testing or dependency injection problems without an interface, but I can talk about why we shouldn’t be bending the source code to fit the tool or methodology.
I talked about the purpose of unit testing before, and one of the key benefits being that unit tests help guide your design. Unit tests help us to decouple our application and consolidate our classes to single responsibilities by making it really painful to try and unit test classes with multiple dependencies.
Interfaces are kind of a shortcut that allows us to get rid of having lots of dependencies in a class.
When we turn a reference to a concrete class into an interface reference, we are cheating the system. We are making it easier to write a unit test by pretending that our class is decoupled because it references an interface instead of a concrete class. In reality it is not decoupled it is actually more coupled because our class is coupled to an interface which is coupled to a class. All we did was add a level of indirection.
Dependency injection promotes the same problem of interface abuse. At least it does in the way it is used in C# and Java today. Creating an interface solely for the purpose of being able to inject the only implementation of that interface into a class creates an unnecessary level of indirection and needlessly slows down the performance of our application.
Don’t get me wrong. Dependency injection is good. I’ll save the details for another post, but I believe dependency injection’s real benefit is when it is used to control which implementation of an interface is used, not when there is only one implementation of an interface.
Ultimately, I can’t give you a good answer of how do you unit test or use dependency injection without abusing interfaces. I think you can reduce the abuse by choosing to split apart classes and actually reduce dependencies rather than simply creating an interface and injecting it into the class, but you are still going to have the problem that a Car has an Engine and if you want to unit test the car, you are either going to have to use the real engine or find a way to mock it.
The key problem here is that interfaces are part of the language, but unit testing and dependency injection are not. We are trying to make them fit in with the language by using a trick. The trick is we create an interface to provide a seam between classes. The problem is that we dilute the potency of an interface by doing so. What we really need is a language supported seam to allow us to easily replace implementations of concrete classes at runtime.