3 Reasons to Use Interface That Only One Class Implements


Interfaces have a simple job to fulfill in code. They help you tie the code to the behavior instead of the implementation. If you write multiple classes executing similar actions in slightly different ways, an interface gives them a shared upper layer to hide that diversity.

The importance of an interface with multiple implementations is not up for debate. But does the same stand for interfaces with only one implementation?

There are three main reasons to use an interface that only one class implements:

  1. Interfaces bind the class into a contract
  2. Interfaces make unit testing easier
  3. Interfaces add minimal overhead.

At first glance, it seems like a waste of your effort to use an interface when only one class ever implements it. If you look a little closer, though, the answer is not as straightforward.

From both design and development points of view, you can find plenty of reasons to use interfaces even when they appear unnecessary.

When are you going to need it?

You Aren’t Gonna Need It, or YAGNI is one of the most popular mantras you hear in programming. It tells developers to stop adding functionality you may need in the future to the code today. It prevents you from incurring costs that may not pay off when you arrive at the actual version of the imagined future.

not needed feature
Not needed feature adds the unnecessary cost of repair

If you create an interface for a single class, anticipating more future implementations, it indeed becomes a bad practice, according to YAGNI. But is it the only reason for wanting such an interface?

No.

In fact, you can find many instances a single-implementation interface becomes useful when designing, writing, and testing software.

#1 Interfaces bind the class into a contract 

An interface is a contract between a class and its code. It specifies the behavior the objects of the class implementing the interface must show. The class has to implement the interface’s methods and properties with no exception.

The guarantee this contractual binding provides allows other parties to trust the object’s functionality even if they don’t know the implementation details. At the same time, it gives your system the benefit of abstraction to expose only what’s needed and nothing more.

Does such a contractual guarantee justify creating interfaces with only one implementation, though? Yes, there are certain scenarios where this approach becomes useful. For example:

  • You want to expose different behaviors of the same class to two user groups.
  • You are creating a public library.

Exposing different behaviors of a class to two use cases

Think of a class with several public methods. Not all of them are intended for the use of outside parties. Some of them only provide services to internal users.

In this situation, creating an interface allows you to expose the methods useful to the outside world while hiding internal ones.

In a slightly different scenario, you may want to expose two different functionalities of a class to two different use cases.

For example, the following Drawing class contains methods to print and resize the drawing. But you only want to permit access to each behavior to only one user group. Its additional functionality must remain hidden from the other group.

If you create two interfaces, Printable and Resizable, you can show two different views of the same class to separate classes as required.

interface Printable
{
    void PrintDrawing();
}

interface Resizable
{
    void ResizeDrawing();
}

public class Drawing : Printable, Resizable
{
    public void PrintDrawing()
    {
        //implementation
    }

    public void ResizeDrawing()
    {
        //implementation
    }
}

Creating a public library

Public libraries are a way of relying on someone else’s code to quickly achieve a time-consuming and complicated task without reinventing the wheel.

Library users are less interested in understanding how you handle the internal implementation. Instead, they take a black box approach to see if the library behaves as it promises with the help of extensive testing. The more abstract the complexities of the implementation are, the happier the users will be.

This need for abstraction has made it a standard practice to wrap the library class that implements publicly-accessible methods using an interface. It also offers you the freedom to change the underlying implementations over time without causing the user to modify their code.

#2 Interfaces make unit testing easier

Unit testing is an essential part of any software development workflow. It helps you write well-functioning programs and identify bugs. It’s also one of the cases that nudge developers to create interfaces even for classes without alternate implementations.

Isolation is a principle that plays a key role in unit testing. It prompts you to isolate each unit from external dependencies before running the tests. For example, when testing a method conducting I/O operations, an alternative implementation should mimic the dependent class’s behavior to break the actual dependency. This alternative is often a mock or a stub.

Interfaces are commonly used for creating a mock or stub class for a concrete implementation. When relying on a testing framework like Moq in C#, the interface becomes necessary for automatically setting up a mock/stub for the external dependencies.

But how does an interface helps the creation of a mock or a stub? The principle behind this is simple.

The existence of the interface ties your code to the abstraction, not the concrete implementation. It allows you to write another class, called a mock or a stub, to mimic the concrete class’s behavior without throwing errors. This mock/stub class uses hard-coded values instead of results from actual I/O calls to help the tested method isolate.

As long as the mock/stub honors the contract the interface specifies, the code doesn’t notice any changes in its behavior. This allows the tests to complete without being impacted by errors from external dependencies.

If you have similar classes with dependencies on outside sources, creating interfaces to abstract their behavior becomes a massive advantage during unit testing.

#3 Interfaces add minimal overhead

Yes, setting up interfaces for classes without alternate implementation consumes time and effort. But with their lightweight and simple boilerplate, how much overhead does it add to your coding process? Perhaps, extra 1-2 minutes for each interface you create.

Considering the overflowing tooling support modern IDEs provide for extracting an interface from a class, this overhead is almost negligible. In Visual Studio, for example, you can give the extracted interface a name and select the required methods in a matter of a few keystrokes.

Hence, the overhead of using an interface in place of its concrete class should never play a significant role when making your final decision.

The answer isn’t using interfaces for every class

There are obvious benefits and use cases for creating interfaces for classes without alternate implementations. But it doesn’t mean you should give interfaces to every one of them.

For example, you shouldn’t use interfaces with internal classes that aren’t visible outside the assembly in C#. Also, if the single concrete class engages only in activities that aren’t error-prone or outward-facing, you should think twice before giving it an interface. Otherwise, it can unnecessarily complicate your code and make it difficult to read and maintain.

Extract Interface refactoring is an excellent method for avoiding such over-creation of interfaces. It urges you to start development with only the classes and create interfaces during the refactoring phase. The criteria you follow to determine whether an interface is needed is breaking dependencies between concrete classes.

If you use Visual Studio 2022, performing the Extract Interface refactoring is really easy.

Select the class you want to create an interface for, click on the blue brush, and select Extract interface…

extract interface 1

Then choose the methods that should go to the interface.

extract interface 2

If one class uses an object from another class in its code, you can put the second one behind an interface to break the dependency between the two. It ties the code to the abstraction, allowing you to isolate units, especially during testing.

After the Extract Interface refactoring, you can add more interfaces to special classes that work as connecting points for outside entities, like in public libraries or independent modules.

Conclusion

Interfaces are a clean and straightforward way of adding abstraction to the code in Object Oriented Programming. Its benefits are not limited to classes with multiple alternate implementations, though.

Even an interface with only one concrete implementation can help you to break unwanted dependencies in code and expose only its functionality to outside users. This trait becomes particularly useful to developers when writing unit tests and developing public libraries.

It may not always be easy to realize when and if such interfaces become unnecessary. But if you combine what you learned here with Extract Interface refactoring, you’ll be able to fine-tune a recipe for creating “just enough” interfaces.

Recent Posts