What is an Interface?
An interface is a component that another component can interact with to perform one or more operations.
Most beginners in programming encounter the term interface when they dive into object-oriented programming or clean code principles like SOLID. This article will help you understand what an interface is, irrespective of the programming language that you use. It will also cover the concept of programming to an interface, not an implementation.
An Interface is like a USB Port
What are the similarities between an interface and a USB port? What can we learn about interfaces from a USB port?
If you have a smart phone with a USB charging port, you will find that aside from using that port for charging your device, you can
plug in an OTG device into the same port and view the files in the OTG device’s memory through your phone,
plug in an OTG bluetooth device connected to a wireless mouse and operate your phone with the mouse.
The USB port component of your phone serves as an interface through which other components like a charger and an OTG device can interact with your phone to perform one or more operations.
An interface is a component that another component can interact with to perform one or more operations.
The manner in which the USB Port is shaped makes it a suitable point of interaction between your phone and other components. As a result, it demands that whatever is going to interact with the phone through the USB port should have the shape that you see on the OTG device and the charger.
The Behaviour of Interfaces
Did you notice that the USB port does not detemine the specific use case of the components that interact with it? It does not determine whether a component that interacts with it will
- share the contents of its memory with it as in an OTG memory device
- charge it as in the case of a charger
- control the screen as in the case of the OTG bluetooth device
These components have specific built-in behaviour and the USB port interface accommodates them all if they fit into the port. This is an important behaviour of interfaces (including programming interfaces) that should be kept in mind.
Interfaces do not determine the behaviour of the components that connect with them. They only determine how components can interact through them.
From Analogy to Code
Using a phone’s USB port as an analogy for an interface, the Phone
class in the code snippet below has an acceptUSBDevice
method. When a component or device is connected to the port, acceptUSBDevice
is executed.
The acceptUSBDevice
accepts an argument called component
. Any component that will be passed into the acceptUSBDevice
method must fit the shape of the USBComponent
interface. In other words, the component must have the performOperations
method.
interface USBComponent {
performOperations(): void;
}
class Phone {
acceptUSBDevice(component: USBComponent) {
component.performOperations();
}
}
class USBCharger {
performOperations() {
console.log(
"Transferring electric current from the socket to the phone's battery"
); }
}
class OTGBluetoothMouse {
performOperations() {
console.log("Currently pointing at position (X, Y) on the phone's screen");
}
}
class OTGMemoryDrive {
performOperations() {
console.log("Currently displaying files on the device");
}
}
const phone = new Phone();
const charger = new USBCharger();
const bluetoothMouse = new OTGBluetoothMouse();
const memoryDrive = new OTGMemoryDrive();
phone.acceptUSBDevice(charger); // 1
phone.acceptUSBDevice(bluetoothMouse); // 2
phone.acceptUSBDevice(memoryDrive); // 3
You can try this out in this programming to an interface TypeScript example
Each component, the charger, the bluetooth mouse and the memory drive, implement the performOperations
method in its own specific way. As long as the components fit the shape of the USBComponent
interface, by having a performOperations
method, the interaction will work perfectly.
Programming to an Interface
To program to an interface means to write code that is more concerned about the shape of the data that it handles than the operation that it executes. This type of code is flexible enough to perform multiple implementations.
To illustrate this with code, let us imagine that our USB port was programmed to an implementation and not an interface. If this was the case, the acceptUSBDevice
method will not have its component
parameter programmed to the USBComponent
interface, but to a specific implementation such as USBCharger
, OTGBluetoothMouse
or OTGMemoryDrive
. We will have have this in code instead
class Phone {
acceptUSBDevice(component: USBCharger) {
component.transferCurrent();
}
}
This works. The issue is that the acceptUSBDevice
is tied to only being used with a USBCharger
. No other component can be used with the USB port because when the transferCurrent
method is executed by acceptUSBDevice
, it will either not work or it will throw an error. Other components like the OTG memory drive do not have the transferCurrent
method implementation.
In the case of the last snippet, acceptUSBDevice
is programmed to an implementation - the USBCharger
implementation, and that has made the code less flexible to accommodate other use cases of the acceptUSBDevice
method. The code snippet in the programming to an interface TypeScript example shows how to program to an interface.
Conclusion
You can find another code illustration for how to program to an interface in this Salary Manager playground example with notes attached to it.
Cheers!