Introduction
There are a lot of guides on how to write good code. Many organizations implement static code analysis to verify and improve code quality. Developers become more and more conscious of what clean code is, SOLID, design patterns, GRASP… So many instructions how to code so we start to forget what this is all about.
Why We Need Interfaces?
The very first use case of an interface is to describe behavior of an object that implements it. If a class Dog
implements interface IAnimal
, we are assured it (not always) can Move
and Eat
. Of course, a Dog
can also Bark
or Sit
, but this is specific implementation that differs from a Bird
that can Fly
.
A more complex use case of an interface is a polymorphism (often combined with dependency injection). If our code depends on abstraction (interfaces in this case), it is more flexible. We can use any class we want, that implements the necessary interface.
Thanks to interfaces, we can reduce coupling between classes. If your implementation is based on abstraction (such as interfaces), you could change the output of your application without changing the source code, just by changing implementation of the interface.
So Why Shouldn’t We Use Them?
At the beginning of my development journey, I didn't ask many questions, I just did what other, more experienced colleagues did. But after some time, I started to see flaws in design and implementations.
How many times have you seen such an interface:
namespace Calculator
{
public interface IMathCalculator
{
int Sum(int a, int b);
int Subtract(int a, int b);
}
}
With implementation like this:
namespace Calculator
{
public class MathCalculator : IMathCalculator
{
public int Sum(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}
}
Or even something like this:
public class CalculatorConfigProvider : ICalculatorConfigProvider
{
public double GetPiValue()
{
return 3.14;
}
}
public interface ICalculatorConfigProvider
{
double GetPiValue();
}
Ok, so what is possibly wrong in this code?
In my opinion, this interface is completely not necessary. Interface suggests possible multiple implementations. In this case, we have only one implementation with the exact name of the interface. The interface and its implementation is in the same namespace so it is not a problem to make use of a concrete implementation instead of an abstraction.
What is more, if you use IoC container (e.g., Ninject), you need to register all classes with their interfaces. This is another thing to remember and it may look like this:
Kernel.Bind<ICalculatorConfigProvider>().To<CalculatorConfigProvider>();
Kernel.Bind<IMathCalculator>().To<MathCalculator>();
Kernel.Bind<ICalculatorResolver>().To<CalculatorResolver>();
The bigger a project is, the more entries are added when registering IoC Container. Initialization classes become bigger and harder to maintain. If you need to register dependencies in two different scopes (e.g., request scope for WebApi and Named scope for some asynchronous endless loop), you need to register your dependencies twice and it is a disaster.
Real Problem - Business Logic
We were talking about good practices, coupling, maintainability and so on. But I think the real problem is when you deal with your domain and business logic. This is a crucial part of the application - sometimes, a key point and the purpose of the application or even the whole company. This part contains some serious calculations, algorithms or laws.
If you add an interface to such class and then inject this interface, you have absolutely no control of how it would be used. Especially when you are making an API or a library used by another application.
A simple example of an OrderGenerator
that is executing domain logic of generating an Order
from Products
shows an idea:
public class OrderGenerator
{
private readonly TaxCalculator calculator;
public OrderGenerator(TaxCalculator calculator)
{
this.calculator = calculator;
}
public Order Generate(IEnumerable<Product> products)
{
var order = new Order();
foreach (var product in products)
{
var price = calculator.CalculatePrice(product);
order.AddPosition(product, price);
}
return order;
}
}
TaxCalculator
is injected into OrderGenerator
and it is making some calculations based on product type, tax prices and country laws.
public class TaxCalculator
{
public Price CalculatePrice(Product product)
{
}
}
With this implementation, you are certain that OrderGenerator
will calculate the price correctly for a given product.
Now consider that on a Code Review, some Senior-Expert suggested adding an interface to a TaxCalculator
and injecting an ITaxCalculator
to the OrderGenerator
. Seems pretty easy and it sounds like a good idea. You think of Interface segregation from SOLID, you know that interfaces are good. The code looks good too. :)
public interface ITaxCalculator
{
public Price CalculatePrice(Product product);
}
Now, OrderGenerator
became open for modifications. What does it mean? It means that you can implement a new TaxCalculator
, e.g., NoTaxCalculator
that implements ITaxCalculator
and returns 0
as a price.
The problem is that it is a crucial part of your application. Or your ecosystem. Or your Company.
And now, someone can break it. :)
But I Need Interfaces To…
Well… I have heard some justifications why it is necessary to add interfaces to classes. Here are some of them:
- Register classes in IoC Container to inject them into another classes
- Mock classes in Unit Tests
- Write clean code and reduce coupling between classes
But, is it true?
IoC Container should resolve a class when it contains a public
constructor with all parameters that can be resolved by IoC Container.
If you add an interface JUST for a test - don't. Your production code should not be written just to satisfy tests. You can do whatever you want in a test project, but leave the production code. :)
Using interfaces instead of concrete implementations reduce coupling and that's a fact. But registering interface in the IoC Container adds complexity and lowers maintainability so if you use it only once in the application, it equalizes pros and cons in my opinion.
So What Should I Do?
First of all, you should think about meeting all business requirements. :) If you write the cleanest code of all time, but don't satisfy your business, then the application is useless.
Interfaces have great value and they should be used in every application. Thanks to interfaces, you can use polymorphism, add many patterns such us Strategy, Factory, Command and so on. You can inverse dependencies (D in SOLID) so if your domain uses repositories or adapters, you can inject interfaces and the implementation should be in a separate layer.
But as everything, you should use them wisely. :) Not all classes should have their own interfaces. This is not the only solution.
History
- 7th August, 2019: Initial version