How does TypeScript support OOP?
TypeScript, as a superset of JavaScript, brings robust static typing and advanced features that significantly enhance its support for Object-Oriented Programming (OOP) paradigms. It builds upon ES6 classes and introduces new constructs like interfaces and access modifiers, making it an excellent choice for developing large-scale, maintainable object-oriented applications.
Classes
TypeScript fully supports ES6 classes, which serve as blueprints for creating objects. Classes encapsulate data (properties) and methods (functions) that operate on that data. TypeScript enhances classes with strong typing, ensuring type safety for properties and method parameters/return types.
class Car {
brand: string;
model: string;
year: number;
constructor(brand: string, model: string, year: number) {
this.brand = brand;
this.model = model;
this.year = year;
}
displayInfo(): string {
return `Brand: ${this.brand}, Model: ${this.model}, Year: ${this.year}`;
}
}
const myCar = new Car('Toyota', 'Camry', 2023);
console.log(myCar.displayInfo());
Inheritance
Inheritance allows a class (subclass or derived class) to inherit properties and methods from another class (superclass or base class). This promotes code reuse and establishes an 'is-a' relationship between classes. TypeScript uses the extends keyword for inheritance and super() to call the parent class's constructor.
class ElectricCar extends Car {
batteryLifeKm: number;
constructor(brand: string, model: string, year: number, batteryLifeKm: number) {
super(brand, model, year); // Call the parent constructor
this.batteryLifeKm = batteryLifeKm;
}
displayInfo(): string {
return `${super.displayInfo()}, Battery Life: ${this.batteryLifeKm}km`;
}
}
const tesla = new ElectricCar('Tesla', 'Model 3', 2024, 500);
console.log(tesla.displayInfo());
Interfaces
Interfaces are a core feature for defining contracts in TypeScript. They describe the 'shape' that an object or a class should have, specifying properties and method signatures without providing their implementation. Interfaces are crucial for achieving polymorphism and ensuring type safety across different parts of an application.
interface Vehicle {
brand: string;
model: string;
displayInfo(): string;
}
class Motorcycle implements Vehicle {
brand: string;
model: string;
engineType: string;
constructor(brand: string, model: string, engineType: string) {
this.brand = brand;
this.model = model;
this.engineType = engineType;
}
displayInfo(): string {
return `Brand: ${this.brand}, Model: ${this.model}, Engine: ${this.engineType}`;
}
}
const myBike: Vehicle = new Motorcycle('Harley-Davidson', 'Iron 883', 'V-Twin');
console.log(myBike.displayInfo());
Access Modifiers
TypeScript introduces public, private, and protected access modifiers, which are not natively present in JavaScript. These modifiers control the visibility and accessibility of class members, supporting encapsulation.
public: Members are accessible from anywhere.private: Members are only accessible within the class they are declared in.protected: Members are accessible within the class and by subclasses.
class Account {
public accountNumber: string;
private balance: number;
protected ownerName: string;
constructor(accountNumber: string, initialBalance: number, ownerName: string) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.ownerName = ownerName;
}
public getBalance(): number {
return this.balance;
}
protected deposit(amount: number): void {
this.balance += amount;
console.log(`Deposited ${amount}. New balance: ${this.balance}`);
}
}
class SavingsAccount extends Account {
private interestRate: number;
constructor(accNum: string, initialBal: number, owner: string, rate: number) {
super(accNum, initialBal, owner);
this.interestRate = rate;
}
public applyInterest(): void {
const interest = this.getBalance() * this.interestRate;
this.deposit(interest); // Can access protected deposit method
console.log(`${this.ownerName}'s savings account applied interest.`); // Can access protected ownerName
}
}
const myAcc = new SavingsAccount('12345', 1000, 'Alice', 0.05);
console.log(myAcc.accountNumber); // Public
// console.log(myAcc.balance); // Error: Property 'balance' is private
// console.log(myAcc.ownerName); // Error: Property 'ownerName' is protected
myAcc.applyInterest();
Abstract Classes
Abstract classes cannot be instantiated directly and often contain one or more abstract methods. Abstract methods are declared without an implementation and must be implemented by concrete subclasses. This forces subclasses to provide specific behaviors, enforcing a common structure.
abstract class Shape {
abstract calculateArea(): number;
abstract getShapeName(): string;
display(): void {
console.log(`This is a ${this.getShapeName()} with area ${this.calculateArea()}`);
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
getShapeName(): string {
return "Circle";
}
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
calculateArea(): number {
return this.width * this.height;
}
getShapeName(): string {
return "Rectangle";
}
}
const myCircle = new Circle(10);
myCircle.display();
const myRectangle = new Rectangle(5, 8);
myRectangle.display();
Polymorphism
Polymorphism, meaning 'many forms,' allows objects of different classes to be treated as objects of a common type (e.g., through a base class or an interface). TypeScript achieves polymorphism primarily through inheritance and interfaces, enabling flexible and extensible code.
interface Printable {
print(): void;
}
class Document implements Printable {
title: string;
constructor(title: string) { this.title = title; }
print(): void { console.log(`Printing Document: ${this.title}`); }
}
class Image implements Printable {
filename: string;
constructor(filename: string) { this.filename = filename; }
print(): void { console.log(`Printing Image: ${this.filename}`); }
}
function printItem(item: Printable) {
item.print();
}
const doc = new Document('Report.pdf');
const img = new Image('photo.jpg');
printItem(doc); // Calls Document's print method
printItem(img); // Calls Image's print method
Encapsulation
Encapsulation is the bundling of data (properties) and methods that operate on the data into a single unit (a class), and restricting direct access to some of an object's components. TypeScript facilitates encapsulation through classes and access modifiers (private, protected), allowing developers to hide internal implementation details and expose only necessary interfaces to the outside world.
Generics
While not strictly an OOP pillar, generics in TypeScript are crucial for creating reusable components that work with a variety of types while maintaining type safety. In OOP contexts, generics are frequently used with collections (e.g., Array<T>), factories, or services to operate on different object types without losing type information, enhancing flexibility and type checking.