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 is not the determinant of the specific behaviour or use-case of the components that interact with it? The USB port does not determine whether a component that interacts with it will be an OTG device or a charger. These components come with their specific behaviour/implementation built-in.
If a charger is connected to it, it charges the device through the USB port interface. If an OTG bluetooth mouse device is connected to it, a mouse appears on the phone screen for mouse pointer control. If an OTG memory drive is connected to the port, the phone can be used to view the content of the drive.
This is an important behaviour of interfaces (including programming interfaces) that should be kept in mind going forward.
Interfaces do not determine the exact behaviour or implementation of the components that connect with them. They only determine how components can interact through them.
From Analogy to Code
Let us create a code sample using our analogy of interfaces with a phone’s USB port.
We have Phone
class that has a connectViaUSBPort
method. When a component or device is plugged into the port, the connectViaUSBPort
is executed.
The connectViaUSBPort
accepts an argument identified as component
, which must fit the shape of the USBComponent
interface. Any component that will be passed into the connectViaUSBPort
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 {
connectViaUSBPort(component: USBComponent) {
component.performOperations();
}
}
class USBCharger {
transferCurrent() {
console.log(
"Transferring electric current from the socket to the phone's battery"
);
}
performOperations() {
this.transferCurrent();
}
}
class OTGBluetoothMouse {
moveToPosition() {
console.log("Currently pointing at position (X, Y) on the phone's screen");
}
performOperations() {
this.moveToPosition();
}
}
class OTGMemoryDrive {
showFiles() {
console.log("Currently displaying files on the device");
}
performOperations() {
this.showFiles();
}
}
const phone = new Phone();
const charger = new USBCharger();
const bluetoothMouse = new OTGBluetoothMouse();
const memoryDrive = new OTGMemoryDrive();
phone.connectViaUSBPort(charger); // 1
phone.connectViaUSBPort(bluetoothMouse); // 2
phone.connectViaUSBPort(memoryDrive); // 3
You can try this out in this TypeScript Playground
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, which is to have a performOperations
method, the interaction will be fine.
Programming to an Interface, not an Implementation
To program to an implementation and not an interface means to write code that is tied to one implementation, and not 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 connectViaUSBPort
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 {
connectViaUSBPort(component: USBCharger) {
component.transferCurrent();
}
}
This works. The problem now is that the connectViaUSBPort
is tied to only being used with a USBCharger
. We cannot plug in any other component because when the transferCurrent
method is executed by connectViaUSBPort
, it will either not work or it will throw an error because those other components do not have the transferCurrent
method implementation.
In the case of the last snippet, we have programmed to an implementation - the USBCharger
implementation, and we have made the code less flexible to accommodate other use cases of the connectViaUSBPort
method.
Conclusion
You can find another code illustration in this Salary Manager playground example with notes attached to it for your interest.
Cheers!