Abstraction Unveiled: What really is an Interface?

Intro

A common way to explain the programmatic construct known as the Interface is to say that they are contracts that bind a class to a set of functions. So if you bind a Dog class to an Animal class that has functions like Eat() and Speak(), then that Dogclass must implement both those methods. In the case of a dog, Eat() might involve eating dog food and Speak() would likely be a bark. Cats eat different food from a dog as well as “speak” differently so for Eat() it might eat a mouse, and Speak() would likely be a meow.

What I hate about this way of describing an interface is that this only really tells us what an interface is and how it’s used at the surface level, but it speaks nothing of what they really are at a deeper level and why we use them. What’s worse, I am not kidding when I say this is often taught using the Dog-Cat and Animal analogy that isn’t even practical and doesn’t properly explain why we care about interfaces and abstraction. This always reminds me of how we are taught in high school of the mitochondria and how we’d simply regurgitate “The powerhouse of the cell” without actually knowing what “powerhouse” even means! Heck, I won’t pretend to know what it actually means, I have no idea! But I do at least know what an interface is, and I can explain it well beyond the simple definition of a “contract”. To understand what an interface is, we first need to understand the general term of “Interface” and how it relates to abstraction in a practical sense.

What is Abstraction

The Latin origin of the word “Abstraction” is “Abstrahere” which means to “Draw away from”. In a sense, whenever you create an abstraction, you’re drawing away from the specifics of a concretion and only holding on to the fundamental traits that give the concretion purpose. For example if you look at a stick, you might not think much of it at first until you need to reach for an object that is too far away. Now the stick is a tool that can be used to extend one’s reach. What you’ve done here is you’ve Drawn away from the specifics of a “stick” and held onto only what you care about: “a long tool, useful for reaching for things”. Notice how you create this abstraction only when you need it. That stick might become a weapon you need to defend yourself if it happens to be heavy enough, in which case you’ve created a new abstraction. But a stick is not the only tool that can extend one’s reach nor is it the only thing that can be used as a weapon. A hammer can also extend your reach (it can also be a weapon), there is also the back scratcher, and even the ruler.

Put simply, an abstraction defines a pattern among a group of concrete things. Whenever you create an abstraction, you are effectively chunking a set of concrete constructs into a related group and often times, you give it a name. “Math” is an abstraction defined by a set of rules that involve numbers; I don’t have to explain what “Math” is to you because simply by saying it, your mind conjures up the concept holistically. Abstractions give humans a common language by which we communicate and interact with the world. I like to simplify the uses of abstraction to 3 basic use cases:

  1. To chunk complexity into a single entity/symbol/name to make it easier to understand and work with
  2. To swap between different implementations of the given abstraction as needed
  3. To defer an important design decision for later

I’ve already given you examples of numbers 1 and 2. Number 1 was the “math” example and number 2 was the “stick” example being replaceable with a hammer, or a ruler or a backscratcher because all of these things are things that can extend your reach. The best real world example of number 3 is the Outlet (although, this is also an example of number 2) that people use to plug many different kinds of devices to. You can plug your toaster, your cellphone and even a computer to it and it all just works. We created outlets to defer the overall use of a house to the homeowner. Because the house is effectively an empty shell with a bunch of outlets, the homeowner can customize the functionality of their house with whatever they want that requires power. If you want a TV, you can buy one and plug it into one of the outlets in your house. If you want different colored lights in one of your rooms, you can do so with the outlet. The builders who built your home didn’t have to do any of that for you, and they shouldn’t. You should be able to use your home however you want, and so the builders defer those decisions to you, the owner.

This is important to understand as we dive deeper into interfaces in general.

What Really is an Interface

The definition of an interface is quite literally in the name: “inter” is a prefix that means “between” and the word “face” can be interpreted as the first thing that you see when you look at a thing. When you look at a person’s face, you can often times get a glimpse of what is going on in their heads through their facial expression. Thus, like any good face, you should get an idea of what is happening behind the face just by looking at it, while also not being able to know about any details of what’s going on. If you mix the concepts of “inter” and “face” together, you get a face that lies between 2 things: The user and the system.

The Interface is a construct meant to act as a facade that hides the inner-workings of a larger system and exposes the surface-level functionality of said system. It is the informative boundary between the user and the system. When you want to turn on your tv, you are not concerned with the details of establishing the connection between the tv and your channels or applications. You don’t have to worry about setting up the connection protocol that allows your tv to connect to the internet, and you don’t have to worry about how the individual pixels are set to their appropriate colors as you make selections on your TV screen. All you have to do is take your remote controller, navigate to the Netflix button on your TV Screen, and press the select button on your controller and it automatically takes you to Netflix, where you can choose which show or movie you want to watch. Believe it or not, through this whole process, you are working with multiple interfaces: The remote controller which itself is an interface, the menu you navigate to choose between Netflix and Hulu or any other application for that matter, and the Netflix interface that lets you choose a movie or show.

The controller has a set of buttons you can press that all send messages to your TV. The reason it’s considered an interface is because the controller acts as a facade to how you, the user, can interact with your TV. Today, TV remotes usually have 4 directional arrow buttons that allow you to navigate the User Interface of your TV and a central button that lets you select a menu Item in your screen. They also almost always come with volume buttons that let you change the volume of your TV.

Note

These common traits of the controller are an important bit of info for talking about interfaces in terms of the programmatic use of the term Interface since interfaces in Programming often expose common functionality of those that implement them.

There are many different kinds of Remotes, and a lot of them look different from one another as well as have “extra” functionality that not all other remotes have.

Pasted image 20251030190127.png
Above is an older remote controller

Pasted image 20251030190133.png
An this is a newer controller

All of these remote controllers can be thought of as “implementations” of the abstract “Remote Controller” interface. All controllers no matter how different still abide by the same contract: Navigational Arrow buttons, a Select button and a set of Volume buttons. The extra bells and whistles are things most users don’t have to deal with and can get by just fine if they stick with the basic interface of the controller.

The Controller: A Common Example of an Interface

What makes the controller so useful is that it is a sort of “common language” almost all humans speak that lets you immediately know what it’s used for and how to use it. When you look at an image of one, you don’t have to think about the details of how it works, you just know what it is, what it’s for and how to use it. It’s easy to use because the interface exposes you to it’s very basic functionality: you just have to know that you can raise the volume and navigate the menu screen with the arrow buttons and select button. The controller is a facade; it hides all of the complicated details of how TVs work and Interfaces in programming are exactly the same. Interfaces in programming allow you to determine a set of functions that you’ll expose to your users and hide what you don’t want them to worry about. Interfaces are meant to make it easier to use your complex systems. The only difference between the controller and interfaces such as those in Java or C#, is that in the case of interfaces in programming, you are no longer the user of the interface, but the creator and designer.

Imagine if you were the creator/inventor of the remote controller for the TV, with no context on how a controller should work at all because they never existed until now. You would be responsible for making it work: how does it connect to a TV without using a cable? How does the TV “know” what to do when you press a button? How do you make this complex stuff easy for a regular person to use? This is why people get so confused with interfaces in programming, because instead of being the one who simply uses them, you are creating them and now must design them in such a way that is easy to use for anyone who might use them. In this scenario what you are effectively doing is designing how another programmer should interact with your software. And that “other programmer” can be, and often is, a future version of yourself. However, what can help with this is understanding this reality; you are designing a system to be used by someone else in the future and making it as easy to use as possible matters. Remember that the Remote Controller did not come before the TV. The TV came first and someone decided they wanted to create an interface that makes using the TV easier and more convenient. That is the point of an interface: it abstracts the complexity of a system down to a simple set of concepts for anyone in the domain to use. When you create an interface, you are creating an abstraction to make it easier to use for other programmers. When you create an interface, you are making a deliberate design choice.

When to Create an Interface

As mentioned earlier, knowing when to use an interface is difficult for a lot of people. I believe this is especially true, because of the way we teach interfaces to new programmers. Interfaces are not just contracts you bind classes to; they are a design choice made by the creator of a larger, more complex system. Now that you know the general definition of the term, you may come to realize that even within a class is an “interface” by the broader definition of the term. In C#, you can define which properties and functions are private or public. The public members of the class are the exposed parts of the larger system that users of that class can use to interact with objects that are instances of that class. Your public members are the interface into your classes. Make no mistake, the same way that any class that implements an interface is bound to the functions declared in that interface, the objects that are instantiated from a class are bound to the members of its class. The public members that outside systems can use from an object's class collectively act as the interface by which they interact with the object.

Note

I promise I will get into actual interfaces in this article. But I think it’s very important to explain this in terms of the class-object relationship first, because the public members of a class are also a kind of interface. Once you learn to know an interface and an abstraction when you see them, you will be far better equipped to use actual interfaces as they relate to programming.

So, if both the public members of classes and actual interfaces are interfaces then how do we know when to use one over the other? The way I’d like more people to think of classes and objects in the world of OOP, is that classes are blueprints for systems that contain functionality and state, and for particularly complex systems they will often have state and functionality that you don’t want your future self or potentially other people to ever worry about, because well…They are complex. You want to make your systems as easy as possible to use, so you create a class and give some of the complex functionality a name (this is where functions come in). These systems also often times have internal state. The state of your objects that you want outside users to see should be public, you can make it so that this state is “read only” for those outside your object by making the state private and then making a public getter. This way, those outside are only exposed to the state of your object but can’t tamper with it themselves (that is the job of your underlying complex system: your functions) An example can be seen below of what I’m talking about:
Pasted image 20251030190716.pngIn this example, we have a simple incrementor class that takes a number and increments by some amount. Notice the private state field; I make it private because I don't want external systems to be able to change it themselves without using the exposed parts of the system that only I provide. There is the Number property that acts as a getter for the state field, and I have a few functions that all increment the state in different ways. I do all this very deliberately because I want outsiders who use objects from this class to use the objects in a particular way. Recall when I said that whenever you make an interface, you are making a deliberate design choice. You should think the same way about the public members of your classes; when you make public fields or functions in a class you should be thinking about how you want outside systems to use the class and objects that are instantiated from those classes. Anytime you make a public member in a class, you are making a design choice. Do so responsibly.

When to Create an Actual Interface

So how does all this about a class and its public members relate to when we should use interfaces? The same way that the collective public members of a class act as an interface implemented by objects that are instantiated from them, an interface (the actual interface with the interface keyword) is an interface implemented by classes. The difference is subtle, but it's key to understanding how to use interfaces: objects "implement" their class interfaces, while classes implement their interface interfaces. That is to say, you ought to create an interface whenever you have a very particular way you want outside systems to interact with your classes that implement the interfaces, and since those outside systems can only interact with classes through their instantiated objects, you are effectively creating an even higher layer of abstraction for those systems to use and forcing them to use the higher interface level of abstraction instead of the lower class level. With a class, you can have many instances of that class which is quite useful for when each object needs to handle its own state as well as expose common functionality that all objects of that class share. Classes allow you to treat objects of those classes similarly, but what happens when you have multiple classes that you want to do the same with? That's what interfaces are for. The interface allows you to treat classes of an interface similarly, that is, interfaces allow you to treat classes that all implement the same interface similarly. Recall in the beginning when I said that abstractions are constructs that define patterns among a group of concrete constructs. interfaces do the same thing, and because you’ve grouped a set of classes together by way of the interface you can treat them the same way.

This is a very important distinction because it means that when you create an interface you must do so with the intention of using them. A common rule of thumb is to create an abstraction whenever you find yourself repeating yourself for a 3rd time, so if you have 3 cases of the same method in 3 different classes, it might make sense to make an interface and have those 3 classes implement the interface. This is not necessarily wrong but it is misguided. If you don't intend on treating those classes as a group in some way then you are creating abstraction for its own sake. The idea is to create an abstraction with a purpose: Do you intend to use your classes polymorphically? Are you writing a set of classes you expect future users to extend? Are you deferring an implementation decision for your future self or a future user? The same way a class gives you the opportunity to interact with similar objects in different ways from the same set of class members, interfaces give you the opportunity to interact with objects of similar classes in different ways from the same set of functions each class has in common.

A real-life example would be if you were trying to create a calculator and you needed your calculator to be able to parse a string of math expressions to an actual number. One way to do this is to use a set of nodes that represent different operations of a Mathematical Expression (for example + for addition, — for subtraction, etc.) and then use a system that knows how to create a tree of these nodes based on a string of text. I won’t get into the details of actually parsing the string as that is beyond the scope of this post, but we can start with creating an interface that the parser would use to interact with different objects:
Pasted image 20251030190834.png

The MathExpressionNode interface, is an interface for any class that should act as a node on a tree of Expression nodes. most nodes will represent an operation that a calculator might be able to do, such as addition or subtraction. Here is what an Addition Node might look like:
Pasted image 20251030190849.png

Notice the leftNode and rightNode variables are of type MathExpressioNode which is our interface. This means that the AddNode class doesn't know what nodes it will be given in its constructor; it only knows that the objects it contains must implement the MathExpressionNode interface. I make this choice deliberately because I know that my calculator needs to be able to handle many different possible nodes that the user will provide it. This design decision will make adding new nodes easier (for example a subtraction node or a multiplication node).

The Evaluate() function simply evaluates leftNode and rightNode and adds their result, not knowing or caring how they evaluate.

You will also need a simple Value node that only contains a constant, which in my case I call the FloatNode:
Pasted image 20251030190911.png
This node simply contains a constant that you can define. So in our Program file we might have something like:
Pasted image 20251030190923.png
Our floatNode1 is given the value of 3 in its constructor and the floatNode2 is given the value of 7 in its constructor. If we put these 2 nodes in the constructor of the addNode object, then when it evaluates its value it will return floatNode1 + floatNode2 which gives us 3 + 7. This equals 10 in our case. I'd encourage the reader to try this with a subtraction node.

With these nodes you can eventually create a FormulaEvaluator class that takes a string and parses it into an expression:
Pasted image 20251030190938.pngThe details of how FormulaEvaluator works is well beyond the scope of this post. In this case I am parsing the "2 * 3 + (3^2)" string and turning it into a tree structure that knows how to evaluate each number and operation in order correctly. So it will first evaluate "3^2" (the '^' symbol represents an exponent) by creating an exponent node object and get 9 (it evaluates 3 squared), then it will evaluate "2 * 3" by creating a multiplication node object and get 6, and finally it will take the result of both of those and add them using the same Addnode object we created before. So 6 + 9 equals 15. In case you doubt that I actually wrote the implementation of the FormulaEvaluator here is a demonstration of it at work:
abstracions_unveiled_01.gif
On the right side, you can even see some of the other MathExpressionNodes I created!

Conclusion

Interfaces are used when you want to expose common functionality between classes to be used by outside systems later. In the first Main function example, we do use the specific AddNode and FloatNode objects directly, but the AddNode class uses the interface MathExpressionNode so that you can add any kind of node you want to it. In the second example above the AddNode would have a MultiplicactionNode as its first node and a ExponentNode as its second node. Each of those nodes in turn would contain their FloatNode objects within them and evaluate their values accordingly before AddNode evaluates them all. All of these operational nodes implement the MathExpressionNode interface and only concern themselves with other nodes that implement MathExpressionNode. This way, we use the nodes polymorphically. It also allows us to create an indefinite number of nodes for any given purpose.

Polymorphism is only one use case of interfaces, but there are others and interfaces are certainly not the only tool you use for polymorphism. Inheritance is also a tool used for polymorphism. I already mentioned some of the other uses of interfaces but the general principle is to use them anytime you have a named concept you want to use across multiple classes, and you want future users to take advantage of that common concept. In the case of our example in this post, it was the concept of evaluating a numeric value. That simple concept allowed us to create a complex tree structure made only of objects that implemented that concept to evaluate any possible kind of math expression. interfaces are powerful tools, but their misuse can be detrimental to the architecture of your project. Always remember that an interface is a facade that exposes surface-level functionality of an otherwise complex system.