Abstract Classes

What happens when a class is too general to exist on its own? Master Abstract Classes to build the perfect foundation for your hierarchies.

April 25, 20265 min read12 / 14

Sometimes, an "Idea" is so general that it shouldn't have a physical body. For example, you can have a "Dog" or a "Cat," but have you ever seen a generic "Animal" walking down the street? No.

To represent these general ideas that shouldn't be instantiated, we use Abstract Classes.

The Essentials

The "Incomplete Blueprint" guide:

  1. No Instantiation: You cannot use the new keyword on an abstract class.
  2. Partial Implementation: Abstract classes can have both "Concrete" methods (with code) and "Abstract" methods (without code).
  3. The Base Layer: They serve as the perfect parent for a family of related classes.
  4. Mandatory Overriding: Any child class must implement all the abstract methods of its parent.

The Half-Built House Analogy

Imagine a construction company that sells "Standard Home Foundations."

  1. The Concrete Part: Every foundation comes with plumbing and electrical wiring pre-installed. (Concrete Methods)
  2. The Abstract Part: The company doesn't decide what the kitchen looks like. They leave an "Empty Space" labeled "Kitchen" and tell you: "You must build a kitchen here." (Abstract Methods)

An Abstract Class is that foundation. It gives you the basics for free but forces you to finish the specific details.

Abstract Classes in Action: The Design Foundation

Normal Class

Can be instantiated directly. If it's too general (like "Animal"), you might end up with meaningless objects in memory.

class Animal {
  makeSound() { .. }
}

let a = new Animal();
// What sound does a generic animal make?

Hard to enforce specific behaviors on children without complex overriding.

Abstract Class

Cannot be instantiated. It provides shared "Concrete" logic while forcing children to implement "Abstract" details.

abstract class Animal {
  eat() { /* shared */ }
  abstract sound();
}

class Dog extends Animal { .. }

Guarantees that every "Specific" animal has a sound, while sharing the eating logic.

Code Implementation: The Partial Blueprint

Here is how you combine shared state with mandatory abstract behavior:

abstract class Database { // Concrete attribute: All DBs need a connection string constructor(protected connectionString: string) {} // Concrete method: Sharing the connection logic connect() { console.log(`Connecting to ${this.connectionString}...`); } // Abstract method: Every DB queries differently abstract query(sql: string): void; } class SQLDatabase extends Database { query(sql: string) { console.log(`Running SQL: ${sql} on MySQL`); } } // const db = new Database("..."); // Error! Cannot instantiate const mysql = new SQLDatabase("localhost:3306"); mysql.connect(); mysql.query("SELECT * FROM users");

The Diamond Problem: Why Multiple Inheritance is Forbidden

A common question is: "Why can a class implement multiple interfaces, but only extend ONE class?"

Imagine this scenario:

  1. Class A: Has a method doTask().
  2. Class B: Extends A and overrides doTask() to print "Task B".
  3. Class C: Extends A and overrides doTask() to print "Task C".
  4. Class D: Tries to extend both B and C.

If you call d.doTask(), which one should run? B or C? This ambiguity is known as the Diamond Problem.

To prevent this "Identity Crisis," languages like Java and TypeScript forbid multiple inheritance for classes. Since interfaces only provide the what and not the how, they don't suffer from this problem: if two interfaces have the same method, the child just provides one implementation that satisfies both.

By using abstract classes, you ensure that your code is DRY (Don't Repeat Yourself) while still enforcing a strict structure on all your child classes.

We're almost done with the core pillars. Let's look at some special modifiers: Static & Final.

Practice what you just read.

Abstract Classes: Database Foundation
1 exercise