Adventures in OOP: What is an Interface?
Типичный программист
One of the most mysterious concepts for junior developers to grasp is the interface. Why not just use an abstract class?
What is an interface anyway?
An interface is a special type of coding construct that doesn’t DO anything itself, it only DESCRIBES the general properties and method of another class. We say that another class “implements” that interface, which is to say that it commits to support all of the interaction details defined in the interface.
Let’s think about it this way. There are many types of vehicles, but generally you don’t need to be trained on how to drive each one. That is because they have a common interface. Modern automatic cars will have a gas pedal, brake, steering wheel, push-button ignition, and and a shift to put it in the park, forward, neutral or reverse. As long as the vehicle has those things, you probably can drive it without too much trouble.
It doesn’t matter what type of vehicle it is or what is under the hood — in theory it could be a hovercraft instead of a car— as long as it implements that same interface you should be good to go.
That’s exactly what a software interface is for. If we have a commonly defined way to interact with it, we don’t need to concern ourselves with what exactly it is. We just use it.
Why not just use an abstract class and inherit it?
In many cases you might be tempted to just define an abstract vehicle class. And then anything else will just extend it, taking with it those common properties and methods.
This may work some of the time; however, you will run into use cases where this becomes clumsy and forced to pull off OR it doesn’t work at all.
Dissimilar Objects
One such case is when you have two classes of objects that don’t significantly overlap. Let’s define line items for an invoice. A line item for something purchased might expect a part number and a quantity. But a line item listing a discount might never have either.
interface InvoiceLineItemInterface {
getDescription(): string
getTotal(): number
}
class PurchasedItem implements InvoiceLineItemInterface {
public quantity: number
public unitPrice: number
public upcCode: string
public name: string
public getDescription(): string {
return this.name;
}
public getTotal(): number {
return this.unitPrice * this.quantity;
}
}
class DiscountItem implements InvoiceLineItemInterface {
public description: string
public amount: number
public getDescription(): string {
return this.description;
}
public getTotal(): number {
return this.amount;
}
}
You see here we have two completely separate types of lines that might appear on an invoice. They each have different properties, but because they implement the same interface we could loop through them to print out the invoice without an issue.
There could be cases even more extreme. Let’s say an office chair and a top; they are nothing alike. But… you can spin both!
interface ThingYouCanSpin {
spin()
}
class OfficeChair implements ThingYouCanSpin {
public spin() {
// Wee!!!
}
}
class ToyTop implements ThingYouCanSpin {
public spin() {
// Cool!!!
}
}
Now you can pass around a reference to an item that implements that interface all day long and you’ll know you can spin it!
Similar Objects… Dissimilar Interface
Earlier we talked about vehicles with certain standard ways to operate them. And as long as the interface stays the same then we’re good. However, what about old turn-key ignitions? What if it is a stick shift? There are other types of vehicles like that may not have a traditional steering wheel or pedals. All vehicles, different interfaces.
So when we define our class for a specific model of vehicle, we would inherit the common traits of a vehicle like manufacturer, model, paint color, whatever… but implement the appropriate interface independent of that.
Forgive me, I don’t know much about cars… so I’m going to make stuff up…
abstract class Automobile {
public manufacturer: string
public model: string
public paintColor: string
}
interface KeyStart {
turnIngnition()
}
interface PushStart {
pushStartButton(keyFob: RfidChip)
}
class BeatenDownOldMinivan extends Automobile implements KeyStart {
public turnIngnition() {
// Start 'er up
}
}
class FancyNewSportsCar extends Automobile implements PushStart {
public pushStartButton(keyFob: RfidChip) {
if (keyFob !== null && keyFob.matchesSignature()) {
// Start 'er up
}
}
}
Both are vehicles with similar properties, but we interact with them in different ways. We can inherit all the commonalities in traditional OOP ways by extending a parent class, but at the same time we can describe the differences in how we use them with an interface.
This maximizes consistency AND interoperability between both similar and dissimilar items alike. I hope this helps explain why inheritance of a parent class is solving a different problem than implementing an interface.