devnotes.

Interfaces vs. Abstract Classes

Interfaces and abstract classes (covered next chapter) both define contracts that other classes must fulfill, and beginners often struggle to know when to use which.

InterfaceAbstract class
ImplementationNo method bodies (traditionally); some modern languages allow default methodsCan mix implemented and unimplemented methods
State (properties)Traditionally no instance properties; only method signatures and constantsCan have properties with real values
Multiple inheritanceA class can implement many interfacesA class can typically extend only one abstract class
Relationship modeled'Can-do' — a capability, unrelated to what the class fundamentally is'Is-a' — a genuine categorical relationship
Use caseDefining a capability shared by otherwise-unrelated classesSharing common code among closely related classes

A practical rule of thumb:

  • Use an interface when you're describing a capability that unrelated things might share: Comparable, Serializable, Playable. A Document and a Playlist might both be Sortable, despite having nothing else in common.
  • Use an abstract class when you're describing a category of genuinely related things that share substantial common implementation: Vehicle as a base for Car, Truck, and Motorcycle, which likely share real logic like calculateFuelEfficiency().

In practice, the two are often combined:

interface Drawable {
    function draw(): void
}

abstract class Shape implements Drawable {
    protected color

    function __construct(color) { this.color = color }

    // Shared, concrete implementation for all shapes
    function describe(): string {
        return "A " + this.color + " shape with area " + this.area()
    }

    abstract function area(): number // still must be implemented by each subclass
}

class Circle extends Shape {
    function __construct(color, radius) { super(color); this.radius = radius }
    function area(): number { return 3.14159 * this.radius ** 2 }
    function draw(): void { print("Drawing a circle") }
}

Here Shape provides shared implementation (describe()) while Drawable defines a capability any shape (or anything else) could implement — combining the strengths of both tools.

Interfaces vs Abstract Classes in OOP | DevNotes