If you’re diving into TypeScript and object-oriented programming, understanding abstract classes is crucial for writing flexible, maintainable code. Abstract classes serve as blueprints for other classes while containing some implementation details themselves. Let’s explore how they work and when to use them.
Abstract classes bridge the gap between interfaces and concrete classes, offering a powerful way to share code between related classes while enforcing certain implementation requirements.
Table of Contents
- What Are Abstract Classes?
- Why Use Abstract Classes?
- Syntax and Basic Usage
- Creating Concrete Classes
- Abstract Methods vs Concrete Methods
- Best Practices
- Common Pitfalls and Solutions
- Real-World Examples
- When to Use Abstract Classes vs Interfaces
- Conclusion
What Are Abstract Classes?
An abstract class is a class that can’t be instantiated directly but can serve as a base class for other classes. Think of it as a partially completed blueprint – it provides some implementation details but leaves others to be filled in by the classes that extend it.
Why Use Abstract Classes?
Abstract classes are particularly useful when you want to:
- Share code between several related classes
- Enforce certain methods to be implemented by child classes
- Provide default implementations while keeping some methods abstract
Syntax and Basic Usage
Here’s how to create an abstract class in TypeScript:
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
// Concrete method with implementation
makeSound(): void {
console.log('Some generic sound');
}
// Abstract method - must be implemented by derived classes
abstract move(): void;
}
Code language: JavaScript (javascript)
Creating Concrete Classes
To use an abstract class, you need to create a concrete class that extends it:
class Dog extends Animal {
// Must implement the abstract move method
move(): void {
console.log(`${this.name} is running on four legs`);
}
// Can override concrete methods
makeSound(): void {
console.log('Woof! Woof!');
}
}
Code language: JavaScript (javascript)
Abstract Methods vs Concrete Methods
In abstract classes, you can have two types of methods:
- Abstract Methods:
- Declared without implementation
- Must be implemented by derived classes
- Use the ‘abstract’ keyword
- Concrete Methods:
- Have a default implementation
- Can be overridden by derived classes
- Optional to override
Best Practices
When to Use Abstract Classes
- Base Class Pattern
abstract class HttpService {
abstract baseUrl: string;
protected async get(endpoint: string): Promise<any> {
const response = await fetch(`${this.baseUrl}/${endpoint}`);
return response.json();
}
}
class UserService extends HttpService {
baseUrl = 'https://api.example.com/users';
}
Code language: JavaScript (javascript)
- Template Method Pattern
abstract class DataProcessor {
process(): void {
this.validate();
this.transform();
this.save();
}
protected abstract validate(): void;
protected abstract transform(): void;
protected abstract save(): void;
}
Code language: JavaScript (javascript)
Common Pitfalls and Solutions
Pitfall 1: Trying to Instantiate Abstract Classes
Incorrect:
const animal = new Animal('Generic'); // Error!
Code language: JavaScript (javascript)
Correct:
const dog = new Dog('Rex'); // Works fine
Code language: JavaScript (javascript)
Pitfall 2: Forgetting to Implement Abstract Methods
TypeScript will show an error if you forget to implement any abstract methods in your concrete class. Always ensure all abstract methods are implemented.
Real-World Examples
Form Validation Example
abstract class FormValidator {
abstract validate(): boolean;
protected isEmpty(value: string): boolean {
return value.trim().length === 0;
}
protected isEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
class LoginFormValidator extends FormValidator {
constructor(private email: string, private password: string) {
super();
}
validate(): boolean {
return !this.isEmpty(this.email) &&
this.isEmail(this.email) &&
!this.isEmpty(this.password);
}
}
Code language: JavaScript (javascript)
When to Use Abstract Classes vs Interfaces
Use abstract classes when:
- You need to share code between several related classes
- You want to provide default implementations for some methods
- You need to declare private or protected members
Use interfaces when:
- You only need to define a contract for object structure
- You want to enable multiple inheritance
- You don’t need any implementation details
Conclusion
Abstract classes are a powerful feature in TypeScript that enable you to create flexible, reusable code while maintaining type safety and enforcing implementation requirements. They’re particularly useful in large applications where you need to share code between related classes while ensuring certain methods are implemented.
Remember that while interfaces define what an object should look like, abstract classes can provide both structure and implementation details. Choose the right tool based on your specific needs – if you need to share implementation, go with abstract classes; if you only need to define a contract, use interfaces.
For more TypeScript concepts, check out our guide on TypeScript Interfaces or explore TypeScript Type Assertions to deepen your understanding of TypeScript’s type system.