Today, I’d like to discuss the differences between interfaces, abstractions and .NET interfaces, as well as what the term “implementation details” means.
Since .NET came out, the interface language feature it introduced slowly captured the term “Interface”. Nowadays, the C# keyword “interface” is often taken for the only possible way to introduce an API. The terms “Interface” and “.NET Interface” are used interchangeably by many.
Moreover, it is often considered a bad practice to work with classes directly, especially in the case of Dependency Injection:
public Service(IPersonRepository repository) // “Good” practice
public Service(PersonRepository repository) // “Bad” practice
But is that really the case? To answer this question, we need to step back and recall what the term “Interface” really means.
API is a set of functions client code calls in order to use the functionality a class introduces. There’s a simple thing that differs interface of a class from its implementation details, and that is “public” (and also “internal”) keyword. Every time you add a public method to a class, you change its interface. And every time you call a public method, you use the API of a class.
While .NET interfaces do represent API, i.e. something you can code against, it is incorrect to attribute the term “Interface” exclusively to them. Every class has its own interface that is defined as a set of public methods.
Always depend upon abstractions, not implementations.
I guess this well-known phrase from the Dependency inversion principle added a fair part to the overall confusion. Often, .NET interfaces are used to introduce an abstraction, but the truth is, .NET interfaces don’t automatically make your entities abstract. Abstraction is something that depends on the context it is being used in. .NET interfaces, on the other hand, is just a language feature which can be used – along with the other language features – to introduce an abstraction.
There’s virtually no difference between using a .NET interface and a class. If your code depend on a .NET interface, it doesn’t mean it depends on abstraction. Furthermore, a dependency on a class doesn’t automatically make your code dependent on implementation details.
Relationship between .NET interfaces, classes, API and implementation details
This diagram shows the relationships between those notions. .NET interfaces and classes that are well-defined have a solid and clean interface which make them a good abstraction of the concept they describe.
On the other hand, poorly designed classes tend to expose their internal structure to the clients and thus break encapsulation principles. As for poorly designed .NET interfaces, there’s actually a special term: Header interfaces. It means that, rather than introducing some high-level cohesive set of methods that belong to a specific role, the interface just mimics the structure of a class. And, of course, whether or not a class or .NET interface is designed poorly, depends on the context.
.NET Interfaces and YAGNI
While it’s true that, from a client code perspective, there’s no difference in using a .NET interface or a class, it is not always the case if we look at the whole picture.
How often do you see an interface with a single implementation? Frequently, they are created mechanically just for the sake of “loose coupling”:
public interface IPersonRepository
void Save(Person person);
Person GetById(int id);
public class PersonRepository : IPersonRepository
public void Save(Person person)
/* … */
public Person GetById(int id)
/* … */
But the thing is, just as with abstractions, using a .NET interface doesn’t automatically make your code more decoupled. Also, such approach contradicts YAGNI principle. Adding a new .NET interface should always be justified. Creating a .NET interface with a single implementation is a design smell.
The last thing I’d like to mention is unit testing. There’s some uncertainty in the question of whether or not the creation of .NET interfaces for the purpose of mocking is a smell. I personally tend to avoid such situations, but it’s not always possible. I’ll try to describe this topic in a future post.
- The term “Interface” != .NET interface. Depending on a .NET interface doesn’t mean your code depend on an abstraction.
- Class != implementation details. Depending on a class doesn’t make your code dependent on implementation details.