🔷 TypeScript Q40 / 101

How does TypeScript support OOP?

AI-Powered Answer ✓ Answered

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.

typescript
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.

typescript
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.

typescript
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.
typescript
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.

typescript
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.

typescript
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.