DEV Community

Cover image for How Coding to an Interface Can Increase Your Code’s Flexibility
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

How Coding to an Interface Can Increase Your Code’s Flexibility

A few principles often come to mind when building software. While Program to an Interface doesn’t have an easy-to-remember acronym – unlike e.g. DRY (Don’t Repeat Yourself) – it’s nonetheless useful to keep in mind. In this week’s newsletter, we’ll take an introductory look at what it means, and how you can make your code more flexible by following this advice.

The Problem with Using Explicit Types

Programming to an interface involves working with abstractions rather than concrete implementations. To explain what this means and how it’s helpful, consider the following example:

var primeNumbers = new List<int> { 2, 3, 5, 7, 11 };
Enter fullscreen mode Exit fullscreen mode

Here we have a List containing the first five prime numbers. We can pass it into the following method as an argument to write each number to the console.

void OutputToConsole(List<int> numbers)
{
    foreach (var n in numbers)
    {
        Console.WriteLine(n);
    }
}
Enter fullscreen mode Exit fullscreen mode

So far so good.

But let’s imagine we later receive a new requirement: we need to know when new prime numbers are added to our list. Luckily, we don’t need to worry about the notification system – we can simply change our List to an ObservableCollection and subscribe to its CollectionChanged event.

var primeNumbers = new ObservableCollection<int> { 2, 3, 5, 7, 11 };
Enter fullscreen mode Exit fullscreen mode

Unfortunately, our existing code no longer compiles. OutputToConsole is expecting a List<int>, but we’re now passing an ObservableCollection<int>. This is a quick and easy fix in our example. Afterall, we only have one method parameter type to change. But not all real-world projects are as simple – it could have resulted in a bigger change if we had more code expecting a List<int>. And it would have been simpler too if we didn’t have to make any changes to our method signatures at all.

Adding Resilience

When programming to an interface, we write code around the data contract it needs rather than the object types it interacts with. By relying on abstractions rather than specific implementations, we loosen the coupling between dependencies: our code becomes more adaptable, letting us switch freely between compatible implementations without requiring additional changes.

In our example, OutputToConsole is a simple method: it takes a set of numbers, iterates through it, and writes each number in turn to the console. If we analyse its requirements, we’ll find we simply need a collection where we can access each element once. We could rewrite it using an IEnumerable.

void OutputToConsole(IEnumerable<int> numbers)
{
    foreach (var n in numbers)
    {
        Console.WriteLine(n);
    }
}
Enter fullscreen mode Exit fullscreen mode

By making this change, it doesn’t matter if we declare our prime number collection as:

var primeNumbers = new List<int> { 2, 3, 5, 7, 11 };
Enter fullscreen mode Exit fullscreen mode

or in any of the following ways:

  • var primeNumbers = new ObservableCollection { 2, 3, 5, 7, 11 };

  • var primeNumbers = new HashSet { 2, 3, 5, 7, 11 };

  • var primeNumbers = new[] { 2, 3, 5, 7, 11 };

As the data types in all four of the preceding declarations implement IEnumerable, no changes are required to OutputToConsole.

Summary

Programming to an interface loosens code dependency coupling. By focussing on data contracts, you can freely switch between compatible implementations without further changes to the rest of your code.

By using the simplest interfaces that still let you do what you need to, you’ll have a good chance of writing code that won’t need updating even if your system’s other requirements do.


Thanks for reading!

This article is from my newsletter. If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox (once per week), plus bonus developer tips too!

Top comments (2)

Collapse
 
raddevus profile image
raddevus

That's a very good article that is succinct and makes a great point with great example code. Thanks for writing this up.

Collapse
 
ant_f_dev profile image
Anthony Fung

Thanks for reading.

I've got a few more articles on this concept lined up, each with a slightly different angle. It's a fairly simple concept, but it can be helpful in so many ways.

Back when I was learning C#, I often thought of interfaces as being extra work without much benefit. I've since learned how they can help to add some structure and separation. And it's something I'd like to help others to see too.