How Coding to an Interface Can Increase Your Code’s Flexibility
Tags: csharp, dotnet
Published: 2023-11-22 13:00:00

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:

``` cs
var primeNumbers = new List<int> { 2, 3, 5, 7, 11 };
```

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.

``` cs
void OutputToConsole(List<int> numbers)
{
    foreach (var n in numbers)
    {
        Console.WriteLine(n);
    }
}
```

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](https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=net-7.0) and subscribe to its `CollectionChanged` event.

``` cs
var primeNumbers = new ObservableCollection<int> { 2, 3, 5, 7, 11 };
```

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`.

``` cs
void OutputToConsole(IEnumerable<int> numbers)
{
    foreach (var n in numbers)
    {
        Console.WriteLine(n);
    }
}
```

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

``` cs
var primeNumbers = new List<int> { 2, 3, 5, 7, 11 };
```

or in any of the following ways:

* _var primeNumbers = new ObservableCollection<int> { 2, 3, 5, 7, 11 };_

* _var primeNumbers = new HashSet<int> { 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](https://webdeveloperdiary.substack.com/?sd=pf). 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!