What do you know about OOP Interfaces?

Sounds like a typical interview question isn’t it? Well, be honest, how many times do you build your applications or components using a strong interface-oriented solution? 😏 Is not funny, requires more time and more code, more files… so it really worth it?

Well, in my case I started using them, almost three years after my first job as a developer. I really did not understand how they work, and how to work with them neither. The concept initially is difficult to understand. A lot of technical resources and people claim that an interface is like a contract that our classes have to respect.

But wait a contract? What does it really mean? Respect in which way?… Well let me show you how an interface is our most powerful weapon, I could say that they are the perfect way to way to power up our classes to the next level.

What is an interface?

In real life, a contract is in most cases, an official document that requires a signature and must be respected for all the parts. Well, the same agreement happens between classes and the interfaces, the class provides the signature and the interfaces the contract. So, that class can securely count with those definitions from the interfaces (properties and or methods)

Also, interfaces can be used as types as well, just like a class. In syntax terms, an interface is like a class:

public interface INotificationChannel
{
void Notify();
}

simple interface declaration

See, it is pretty almost the same. However, an interface differs from a class in these aspects:

  1. Method declaration shouldn’t have access modifiers
  2. Method declaration can’t have a body or implementation. That is the idea
  3. An interface can have only methods and properties, but not fields (makes no sense since they are for implementation detail)

So, an interface is purely a declaration, with no implementation. But why would we want that? Well, there are several powerful reasons to implement interfaces, and believe me, as soon we explore the real benefits of them, you will answer on your own. Let’s explore the real power of interfaces.

Interfaces and Polymorphism

But, why do we need polymorphism? Well because we want to build loosely coupled applications. That means our classes should be loosely coupled. The main idea is that if a class needs to be changed, we shouldn’t do it by modifying the minimum implementation detail inside, so always preferred to extend it rather than changing its current logic.

Indeed, that is one of the SOLID principles. We want to follow the Open-Closed principle:

Software entities … should be open for extension, but closed for modification.

But, how interfaces are a pure way to implement polymorphism to build loosely coupled applications? and how they help us preventing our class modification? Well, interfaces are the perfect structure to connect our classes, we reduce the coupling between two or more classes by putting an interface between them, something like a man in the middle.

Interfaces and Extensibility

When we talk about extensibility, an important feature provided by the interfaces is the powerful option of switching the behavior of the application by just changing little pieces of it with a minimal impact.

Think in an interface like a piece of Lego. Imagine an important part of our application, let’s say it’s the piece responsible for storing the application’s logs.

A solution for this using interfaces could be:

public interface ILogProvider {
void Log(string message);
}

Define the interface ILogProvider

Then, create a logger, I want you to notice that I am not creating the actual logger class, is a log provider, a class playing that role:

public class ConsoleLogger : ILogProvider { 	public void Log(string message) {
Console.WriteLine($@"{DateTime.Now} - {message}");
}
}

Create a logger provider

Finally, create the actual logger class and use the ConsoleLogger provider in your application:

public class Logger {	private readonly ILogProvider _logger;	public Logger(ILogProvider logger = null) 
{
_logger = logger ?? new ConsoleLogger();
}
public void Log(string message)
{
_logger.Log(message);
}
}

So let me recap, there are several important points I want you to notice here:

  • The Logger class constructor takes an ILoggerProvider. But, if it is null, by default we want to work with the ConsoleLogger, but I could provide any ILogProvider if want it
  • The Log method in Logger doesn’t care at all about “who” is the log provider, all it cares about is that the provider has a method called Log that takes a string message and that it is invokable.

So at this point, if for some reason we need to change our implementation and extend it to a different approach, we can do it without changing the Logger class. Basically, we need to:

Create a new ILogProvider

public class DataBaseLogger : ILogProvider
{
public void Log(string message)
{
// Connect to database....
Console.WriteLine($@"Logging to database - {DateTime.Now} - {message}"); //...
}
}

And use the Logger differently, for example:

var consoleLogger = new Logger();
consoleLogger.Log("Log to console");
var databaseLogger = new Logger(new DataBaseLogger());
databaseLogger.Log("Log to database");

Two log providers, same logger 🥰

Like switching a piece of Lego. With this approach we are able to extend logger functionality without changing any line internally, we can create any kind of log provider and pass it to. We are not changing our codebase, we are extending it. Also, this approach allows us to decouple our implementation and provide a mechanism to change the implementation detail very easily.

Let’s say that, initially the requirement was to log all the messages at the console. But, if you are an experienced developer, you are aware that customers change their minds constantly, and two months after they want to store logs to an ElasticSearch solution, for instance. With our solution that would be very easy just, pass a new logger provider to our logger class for example:

public class ElasticSearchLogger: ILogProvider 
{
public void Log(string message)
{
// Connect to ES....
//ES storing logs implementation //...
}
}
...
// Usage
var appLogger = new Logger(new ElasticSearchLogger());
appLogger.Log("Logging to ES");

Switching from console logger to ElasticSearch logger. Notice that we didn’t change the logger implementation, we simply supplied a new way to log, therefore we are not impacting the existing tested code, isn’t that beautiful?

Interfaces in action

There are several ways to resolve this, but let me show how using interfaces, is in my opinion, the better.

Remember that we want to provide a loosely coupled solution and make sure that our implementation is open to extension and closed to modification.

So first, let’s create the interface responsible for define the notification channel behavior.

public interface INotificationChannel
{
public void Notify();}

The interface in this context defines the role that must be played by a class. At this point, we are not thinking about creating a class responsible for sending the notification to the UI. Why? Well, we moved the abstraction to a higher abstraction level. We want to include all responsible classes of notifying, to play the same role. It doesn’t matter how they do it, is not an interface responsibility because every class is going to be responsible for the how and the interface for the what

With this approach, we are not worried about other notification channels that can be included in the future. Could be any, and will work with our report process if it is a notification channel. Again, they play a role.

With this in mind, let’s create the class responsible for create and send the report.

public class SalesReporter 
{
public readonly List<INotificationChannel> notificationChannels; public SalesReporter(List<INotificationChannel> notificationChannels)
{
this.notificationChannels = notificationChannels;
}
public void BuildAndSendReport()
{
// implementation detail of extracting and building the report
Notify();
}

private void Notify() {
foreach (var nc in this.notificationChannels) {
nc.Notify();
}
}
}

The implementation detail of extracting and building the report are skipped.

Ok, let’s read the class line by line.

  • We declare a public read-only list of INotificationChannel that holds all the notification channels, that are going to be used later in the notification process.
  • Our constructor takes a list of INotificationChannel and sets it to notificationChannels. At this point, we could include a validation that checks the count of the list or other business requirements. But I skipped it to simplify the solution, just be aware that it is extremely important to guarantee our class state.
  • BuildAndSendReport() is the method responsible for creating the report but, focus on Notify()
  • Notify() does all the magic because it iterates by all notification channels and pushes the notification calling nc.Notify()

Now, let’s create a few notification channels, they are simple classes that implement the INotificationChannel interface

public class UINotificationHelper : INotificationChannel 
{
public void Notify() {
Console.WriteLine("Push notification to application UI");
}
}
public class SlackNotificationHelper : INotificationChannel
{
public void Notify() {
Console.WriteLine("Push notification to slack");
}
}

Classes implementing the INotificationChannel (read as “playing the notification channel role”)

Finally, instantiate the SalesReporter class and send the report

class Program 
{
static void Main(string[] args) {
var notificationChannels = new List<INotificationChannel>() {
new UINotificationHelper()
};
var reportBuilder = new SalesReporter(notificationChannels); reportBuilder.BuildAndSendReport(); Console.ReadLine();
}
}

At this point, we were able to create the report and notify its completion. But, most importantly we were able to decouple the notification and report building process. We passed an initial notification channel (UINotificationChannel) and it is being used internally by the extracting and building report process. The interface allowed us to provide a kind of bridge of gate between each process, with no coupling, no dependencies, isn’t that great?

No? Well, we are not finished yet. And I want you to put special attention at this point, let’s add this method to SalesReporter Class

public void AddNotificationChannel(INotificationChannel notificationChannel) 
{
this.notificationChannels.Add(notificationChannel);
}

The method above exposes a way to extends our class,, we are now able to add more than one notification channel at runtime, without changing our class implementation detail. In simple words, we are not opening our SalesReporter class, go to notify method and add an extra line, we could say it is closed for modification.

Instead, we are now able to extend its functionality by adding any number of notification channels. It doesn’t matter how they do it, we simply know they have to do it, because everyone plays the notification channel role, they respect the contract defined by the INotificationChannel interface. So, we could say it is opened to extension

So, at runtime, we are now able to do something like this.

reportBuilder.AddNotificationChannel(new SlackNotificationHelper());

Isn’t that powerful? Imagine that we add these channels conditionally, depending on user’s permissions for example, or add more notification channels depending on an external resource like store data stored in a database, or results obtained from an API call.

There are a lot of use cases for this approach, this is very simple, but try to think in more use cases, we could improve our implementation even more by decoupling the extracting data process for example to its corresponding interface and so on, the possibilities are wide.

Now you got it? I mean, are you know convinced of the power of the interface? The idea is to decouple everything. In that way you will end up with a lot of classes and interfaces but gain all the benefits and powerful interface features, your codebase is going to be bigger but that is not a problem,

Short and small code does not means the best implementation

there is a tendency to believe that shorter codebases are better, But hell no. I’m planning to create a post about this, be aware.

Interfaces and Testing

Unit testing worths its own post is a huge topic. But I just want to give you a deep interface overview. In C# and other languages like Java, creating unit tests for our classes without interfaces is just a mess, sometimes even impossible.

There are too many important things to keep in mind when we want to create a full-coverage unit-tested application. But the most important is in my opinion, is to provide a mechanism to test our classes separately. A class that has tight dependencies on other classes cannot be isolated, but in order to unit test a class, we need to isolate it.

Do you see? Interfaces kicking in again! Since interfaces, allows us to decouple our class, we could create fake data providers (aka mocks) to create our integration tests for example without changing the class’s internal behavior in order to test it properly.

Interfaces and Inheritance

In C#, using multiple interfaces doesn’t mean multiple inheritances

Fundamentally, inheritance is another OOP pillar, that allows us to re-use code across the class hierarchy. That means we can call parent methods from children, methods that hold a body without the need to type all that code again. But with interfaces, we simply declare the members, the contract, the structure. There is no implementation detail.

So, in every class that implements one specific interface, we need to type all the declarations, providing implementation detail of defined members in the interface. This, along with all the classes that implement a particular interface. So, code is not inherited, is not being re-used. We have to type that code in every new class that implements a specific interface! So, there is no inheritance at all.

It is a different approach if you create a class that derives from a parent class implementing multiple interfaces. That child class inherits all parent class implementation detail, that was defined by the class itself, the interface just provides the structure needed. Remember that interfaces are not responsible for “the how”, is not their business.

Conclusion

  • Interfaces are like a class but without implementation detail.
  • An interface provides the purest and powerful way to implement polymorphism.
  • We want to create loosely coupled classes.
  • Our classes should be closed to modification and open to extension.
  • Interfaces provide an excellent structure to connect our depending classes.
  • An interface is like a role, it provides a higher abstraction layer to work with them as placeholders where there is no implementation.
  • It is ok to have multiple interfaces, but multiple interfaces don’t mean multiple inheritances.
  • Interfaces are key when we want to create fully tested coverage applications.

Well, I hope this post was useful for you. Please let me know any comments and how do you implement interfaces in your daily tasks. Thanks for reading. Happy coding.

Hi, I’m Josue, a software developer and graphic designer. I’m an adventurer and wanderer. Geek passionate about coding stuff. JavaScript and C#.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store