TypeScript Abstract Classes
Creating templates for other classes
🎨 What are Abstract Classes?
Abstract classes are templates that cannot be instantiated directly. They define common structure and behavior that child classes must implement, ensuring consistency while allowing customization. They serve as blueprints for related classes.
// Abstract class
abstract class Animal {
constructor(public name: string) {}
// Abstract method (must be implemented)
abstract makeSound(): string;
// Concrete method (inherited as-is)
move() {
return `${this.name} is moving`;
}
}
class Dog extends Animal {
makeSound() {
return "Woof!";
}
}
const dog = new Dog("Buddy");
console.log(dog.makeSound());
console.log(dog.move());
Output:
Woof!
Buddy is moving
Abstract Class Features
No Instantiation
Cannot create objects directly
abstract class Base {}
// new Base(); // Error!
Abstract Methods
Methods without implementation
abstract class Shape {
abstract getArea(): number;
}
Concrete Methods
Fully implemented methods
abstract class Animal {
move() {
return "Moving...";
}
}
Inheritance
Child classes must implement abstracts
class Cat extends Animal {
makeSound() {
return "Meow";
}
}
🔹 Basic Abstract Class
Define an abstract class with abstract and concrete methods:
abstract class Vehicle {
constructor(
public brand: string,
public model: string
) {}
// Abstract method - must be implemented by child classes
abstract startEngine(): string;
// Concrete method - inherited as-is
displayInfo() {
return `${this.brand} ${this.model}`;
}
}
class Car extends Vehicle {
startEngine() {
return `${this.brand} car engine started with key`;
}
}
class Motorcycle extends Vehicle {
startEngine() {
return `${this.brand} motorcycle engine started with button`;
}
}
const car = new Car("Toyota", "Camry");
const bike = new Motorcycle("Honda", "CBR");
console.log(car.displayInfo());
console.log(car.startEngine());
console.log(bike.startEngine());
Output:
Toyota Camry
Toyota car engine started with key
Honda motorcycle engine started with button
🔹 Abstract Class with Multiple Abstract Methods
Child classes must implement all abstract methods:
abstract class Shape {
constructor(public color: string) {}
abstract getArea(): number;
abstract getPerimeter(): number;
describe() {
return `A ${this.color} shape with area ${this.getArea()}`;
}
}
class Circle extends Shape {
constructor(
color: string,
public radius: number
) {
super(color);
}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
class Rectangle extends Shape {
constructor(
color: string,
public width: number,
public height: number
) {
super(color);
}
getArea(): number {
return this.width * this.height;
}
getPerimeter(): number {
return 2 * (this.width + this.height);
}
}
const circle = new Circle("red", 5);
const rect = new Rectangle("blue", 4, 6);
console.log(circle.describe());
console.log(`Circle perimeter: ${circle.getPerimeter().toFixed(2)}`);
console.log(`Rectangle area: ${rect.getArea()}`);
Output:
A red shape with area 78.53981633974483
Circle perimeter: 31.42
Rectangle area: 24
🔹 Abstract Class for Database Operations
Use abstract classes to define common patterns:
abstract class Database {
constructor(public connectionString: string) {}
// Abstract methods
abstract connect(): string;
abstract disconnect(): string;
abstract query(sql: string): string;
// Concrete method
logOperation(operation: string) {
return `[LOG] ${operation} at ${new Date().toISOString()}`;
}
}
class MySQLDatabase extends Database {
connect() {
return `Connected to MySQL: ${this.connectionString}`;
}
disconnect() {
return "MySQL connection closed";
}
query(sql: string) {
return `Executing MySQL query: ${sql}`;
}
}
class MongoDatabase extends Database {
connect() {
return `Connected to MongoDB: ${this.connectionString}`;
}
disconnect() {
return "MongoDB connection closed";
}
query(sql: string) {
return `Executing MongoDB query: ${sql}`;
}
}
const mysql = new MySQLDatabase("localhost:3306");
console.log(mysql.connect());
console.log(mysql.query("SELECT * FROM users"));
console.log(mysql.logOperation("Query executed"));
Output:
Connected to MySQL: localhost:3306
Executing MySQL query: SELECT * FROM users
[LOG] Query executed at 2024-01-15T10:30:00.000Z
🔹 Abstract Properties
Abstract classes can have abstract properties using getters/setters:
abstract class Employee {
constructor(public name: string) {}
// Abstract getter
abstract get salary(): number;
// Abstract method
abstract calculateBonus(): number;
// Concrete method
getTotalCompensation() {
return this.salary + this.calculateBonus();
}
}
class FullTimeEmployee extends Employee {
constructor(
name: string,
private baseSalary: number
) {
super(name);
}
get salary(): number {
return this.baseSalary;
}
calculateBonus(): number {
return this.baseSalary * 0.1;
}
}
class Contractor extends Employee {
constructor(
name: string,
private hourlyRate: number,
private hoursWorked: number
) {
super(name);
}
get salary(): number {
return this.hourlyRate * this.hoursWorked;
}
calculateBonus(): number {
return 0; // Contractors don't get bonuses
}
}
const fullTime = new FullTimeEmployee("Alice", 80000);
const contractor = new Contractor("Bob", 50, 160);
console.log(`${fullTime.name} total: $${fullTime.getTotalCompensation()}`);
console.log(`${contractor.name} total: $${contractor.getTotalCompensation()}`);
Output:
Alice total: $88000
Bob total: $8000
🔹 When to Use Abstract Classes
Use Abstract Classes When:
- You want to share code among related classes
- You need to define common behavior with some implementation
- Child classes should follow a specific structure
- You want to provide default implementations for some methods
Don't Use Abstract Classes When:
- You only need to define a contract (use interfaces instead)
- You need multiple inheritance (TypeScript doesn't support it)
- Classes are unrelated and don't share behavior