Activity 10: Object-Oriented Programming OOP in TypeScript

Introduction

OOP has been the basic paradigm in the world of software development for producing complex and scalable applications. One popular language that has support for OOP concepts is TypeScript, which is a superset of JavaScript; it adds static typing features and other additional features for better maintainable and readable code.

Object-Oriented Programming is a programming paradigm that focuses on the creation and manipulation of objects to solve complex problems. It emphasizes the concept of classes and objects, encapsulation, inheritance, and polymorphism. These principles enable developers to build modular, reusable, and maintainable code.

Class and Object:

Definition:

In TypeScript, a class is a blueprint for creating objects. It defines the properties and behavior that objects of the class will have. We can create multiple instances of a class, which are known as objects. Objects have their own state (properties) and behavior (methods).

Key Features:

  • Classes encapsulate data and functions.

  • Objects hold the state defined by their class.

How it’s Implemented in TypeScript:

Example Code:

a class using the class keyword.

class Employee {
  name: string;  // Property to store the employee's name
  age: number;   // Property to store the employee's age

  constructor(n: string, a: number) {  // Constructor to initialize properties
    this.name = n;  // Assign name
    this.age = a;   // Assign age
  }
}

create an object using the new keyword.

let emp = new Employee("John", 21);  // Instantiate the Employee class

access the properties of the object using dot notation.

console.log(emp.name);  // Outputs: John
console.log(emp.age);   // Outputs: 24

Encapsulation:

Definition:

Encapsulation is a principle of OOP that enables the bundling of data and methods within a class, hiding the internal implementation details from the outside world. In TypeScript, we can achieve encapsulation using access modifiers.

Key Features:

public: The default modifier. Public members are accessible from anywhere.
private: Private members are only accessible within the class that defines them.
protected: Protected members are accessible within the class that defines them and their subclasses.

How it’s Implemented in TypeScript:

Use access modifiers (public, private, protected) to control access.

Example Code:

Class Declaration: Defines the BankAccount class as a blueprint for creating bank account objects.

class BankAccount {

Private Property: Encapsulates the account balance, preventing direct access from outside the class.

    private balance: number; // Private member to hold the account balance

Constructor: Initializes a new instance with initialBalance, setting the starting value for balance.

    constructor(initialBalance: number) {
        this.balance = initialBalance; // Set initial balance
    }

Public Deposit Method: Allows external code to add funds to the account by updating the balance.

    public deposit(amount: number): void {
        this.balance += amount; // Increase balance by the deposit amount
    }

Public Get Balance Method: Provides a way to retrieve the current account balance without allowing direct modification.

    public getBalance(): number {
        return this.balance; // Return the current balance
    }

Object Instantiation:

const account = new BankAccount(1000); 
// Create a new BankAccount with an initial balance of 1000

Method Usage: Increases the balance by 500 and retrieves the updated balance, demonstrating controlled access to the account's state.

account.deposit(500); // Deposit 500 into the account
console.log(account.getBalance()); // Outputs: 1500
class BankAccount {
    private balance: number;

    constructor(initialBalance: number) {
        this.balance = initialBalance;
    }

    public deposit(amount: number) {
        this.balance += amount;
    }

    public getBalance(): number {
        return this.balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500

Inheritance:

Definition:

Inheritance allows classes to inherit properties and methods from other classes. It promotes code reuse and allows us to create more specialized classes based on existing ones. TypeScript supports single inheritance, where a class can inherit from a single base class.

Key Features:

  • Promotes code reusability.

  • Establishes a hierarchical relationship.

How it’s Implemented in TypeScript: Use the extends keyword to inherit from a parent class.

Example Code:

Base Class Declaration:

  • The Animal class serves as a base class for all animals.

  • The protected modifier allows access to name within the class and its subclasses.

  • The makeSound method provides a default sound for animals.

class Animal {
    protected name: string; // Protected property to hold the animal's name

    constructor(name: string) {
        this.name = name; // Initialize the name
    }

    public makeSound(): void { // Generic sound method
        console.log("Generic animal sound");
    }
}

Subclass Declaration:

  • The Dog class extends the Animal class, inheriting its properties and methods.

  • The super(name) call initializes the name property in the base class.

  • The makeSound method is overridden to provide a specific sound for dogs.

class Dog extends Animal {
    constructor(name: string) {
        super(name); // Call the constructor of the Animal class
    }

    public makeSound(): void { // Override the makeSound method
        console.log("Woof woof!"); // Specific sound for dogs
    }
}

Object Instantiation: This line creates an instance of the Dog class.

const myDog = new Dog("Buddy"); // Create a new Dog instance named Buddy

Method Usage:

  • Calling makeSound on the myDog object invokes the overridden method, outputting "Woof woof!".
myDog.makeSound(); // Output: Woof woof!
class Animal {
  protected name: string;

  constructor(name: string) {
    this.name = name;
  }

  public makeSound(): void {
    console.log("Generic animal sound");
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  public makeSound(): void {
    console.log("Woof woof!");
  }
}

const myDog = new Dog("Buddy");
myDog.makeSound(); // Output: Woof woof!

Polymorphism:

Definition:

Polymorphism enables us to use a single interface or base class to represent multiple related classes. This allows us to write more flexible and extensible code. TypeScript supports polymorphism through inheritance and interfaces.

Key Features:

  • Method overriding (runtime polymorphism).

  • Method overloading (compile-time polymorphism).

How it’s Implemented in TypeScript:

Override methods in derived classes and use function overloading.

Example Code:

Interface Declaration: The Shape interface defines a contract for shapes, requiring any implementing class to provide a calculateArea() method that returns a number.

    interface Shape {
    calculateArea(): number; // Method to calculate area
}

Rectangle Class:

  • The Rectangle class implements the Shape interface.

  • It has private properties width and height, initialized through the constructor.

  • The calculateArea() method calculates and returns the area of the rectangle.

class Rectangle implements Shape {
    private width: number; // Private property for width
    private height: number; // Private property for height

    constructor(width: number, height: number) {
        this.width = width; // Initialize width
        this.height = height; // Initialize height
    }

    public calculateArea(): number { // Implementation of calculateArea for Rectangle
        return this.width * this.height; // Calculate area as width * height
    }
}

Circle Class:

  • The Circle class also implements the Shape interface.

  • It has a private property radius, initialized in the constructor.

  • The calculateArea() method calculates and returns the area of the circle.

class Circle implements Shape {
    private radius: number; // Private property for radius

    constructor(radius: number) {
        this.radius = radius; // Initialize radius
    }

    public calculateArea(): number { // Implementation of calculateArea for Circle
        return Math.PI * Math.pow(this.radius, 2); // Calculate area as π * radius^2
    }
}

Object Instantiation and Method Usage:

  • Instances of Rectangle and Circle are created.

  • The calculateArea() method is called for both shapes, producing the respective areas.

const rectangle = new Rectangle(5, 10); // Create a Rectangle instance
const circle = new Circle(3); // Create a Circle instance

console.log(rectangle.calculateArea()); // Output: 50
console.log(circle.calculateArea());    // Output: 28.274333882308138

Abstraction:

Definition:

Abstraction hides complex implementation details and only exposes essential features of a class.

Key Features:

  • Simplifies complex systems by breaking them into more manageable parts.

  • Facilitates easier maintenance and modification.

How it’s Implemented in TypeScript: Use abstract classes and interfaces.

Example Code:

Abstract Class Declaration:

  • The Vehicle class is declared as abstract, meaning it cannot be instantiated directly.

  • It contains an abstract method start(), which must be implemented by any derived class.

abstract class Vehicle {
    abstract start(): void; // Abstract method with no implementation
}

Subclass Implementation:

  • The Car class extends the Vehicle abstract class, inheriting its properties and methods.

  • It provides an implementation for the start() method, fulfilling the contract set by the abstract class.

class Car extends Vehicle {
    start() { // Implementing the abstract method
        console.log("Car started"); // Specific implementation for Car
    }
}

Object Instantiation and Method Usage:

  • An instance of the Car class is created.

  • The start() method is called on the myCar object, outputting "Car started".

const myCar = new Car(); // Create an instance of Car
myCar.start(); // Output: Car started
abstract class Vehicle {
    abstract start(): void; // Abstract method
}

class Car extends Vehicle {
    start() {
        console.log("Car started");
    }
}

const myCar = new Car();
myCar.start(); // Car started

Additional OOP Concepts to Include

Interfaces:

Definition:

An interface is a contract that defines the structure and behavior of an object. It describes the properties and methods that a class must implement. Interfaces enable us to achieve multiple inheritance-like behavior in TypeScript.

Key Features:

  • Promotes a standard way of interacting with objects.

  • Supports multiple implementations.

How it’s Implemented in TypeScript: Define an interface using the interface keyword.

Example Code:

Interface Declarations:

  • The Shape interface requires a calculateArea() method that returns a number.

  • The Color interface defines a color property of type string.

interface Shape {
    calculateArea(): number; // Method to calculate area
}

interface Color {
    color: string; // Property to hold color
}

Class Implementation:

  • The Rectangle class implements both the Shape and Color interfaces.

  • It has private properties width and height, and a public property color.

  • The constructor initializes all three properties.

  • The calculateArea() method provides the area calculation specific to rectangles.

class Rectangle implements Shape, Color {
    private width: number; // Private property for width
    private height: number; // Private property for height
    public color: string; // Public property for color

    constructor(width: number, height: number, color: string) {
        this.width = width; // Initialize width
        this.height = height; // Initialize height
        this.color = color; // Initialize color
    }

    public calculateArea(): number { // Implementation of calculateArea
        return this.width * this.height; // Calculate area as width * height
    }
}

Object Instantiation and Method Usage:

  • An instance of Rectangle is created, and its type is a combination of Shape and Color.

  • The calculateArea() method is called, outputting the area (50).

  • The color property is accessed, outputting "blue".

const rectangle: Shape & Color = new Rectangle(5, 10, "blue"); // Create a Rectangle instance
console.log(rectangle.calculateArea()); // Output: 50
console.log(rectangle.color);           // Output: blue
interface Shape {
  calculateArea(): number;
}

interface Color {
  color: string;
}

class Rectangle implements Shape, Color {
  private width: number;
  private height: number;
  public color: string;

  constructor(width: number, height: number, color: string) {
    this.width = width;
    this.height = height;
    this.color = color;
  }

  public calculateArea(): number {
    return this.width * this.height;
  }
}

const rectangle: Shape & Color = new Rectangle(5, 10, "blue");
console.log(rectangle.calculateArea()); // Output: 50
console.log(rectangle.color);          // Output: blue

Constructor Overloading:

Definition:

Constructor overloading allows a class to have multiple constructors that can accept different parameters, enabling the creation of objects in different ways based on the provided arguments.

Key Features:

  • Flexibility: Developers can instantiate objects with varying initial states without needing different class types.

  • Readability: It enhances the clarity of code by allowing more intuitive constructors.

How it’s Implemented in TypeScript: In TypeScript, constructor overloading is implemented by defining multiple constructor signatures followed by a single implementation.

Example Code:

Class Declaration and Constructor Overloading:

  • The Person class includes multiple constructor overloads, allowing it to be instantiated with either just a name or both a name and an age.

  • The implementation constructor uses an optional parameter age, indicated by ?, which allows the age to be undefined.

class Person {
    constructor(public name: string); // Overload for name only
    constructor(public name: string, public age: number); // Overload for name and age
    constructor(public name: string, public age?: number) {} // Implementation
}

Display Method:

  • The display() method logs the person's name and age to the console.

  • If the age is not provided, it defaults to "Not specified".

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age || 'Not specified'}`); // Display name and age
    }

Object Instantiation and Method Usage:

  • Two instances of Person are created: one with just a name and the other with both a name and an age.

  • The display() method is called on both instances, producing the respective outputs.

const person1 = new Person('Alice'); // Create a Person instance with only a name
const person2 = new Person('Bob', 30); // Create a Person instance with a name and age

person1.display(); // Output: Name: Alice, Age: Not specified
person2.display(); // Output: Name: Bob, Age: 30
class Person {
    constructor(public name: string);
    constructor(public name: string, public age: number);
    constructor(public name: string, public age?: number) {}

    display() {
        console.log(`Name: ${this.name}, Age: ${this.age || 'Not specified'}`);
    }
}

const person1 = new Person('Alice');
const person2 = new Person('Bob', 30);

person1.display(); // Name: Alice, Age: Not specified
person2.display(); // Name: Bob, Age: 30

Getters and Setters:

Definition:

Getters and setters are special methods that provide controlled access to the properties of a class, allowing encapsulation of the internal state. They enable additional logic to be executed when properties are accessed or modified.

Key Features:

  • They help enforce data hiding and encapsulation by controlling how properties are accessed and modified.

  • Logic can be added to validate values before they are set.

How it’s Implemented in TypeScript: Getters and setters are defined using the get and set keywords.

Example Code:

Class Declaration and Private Properties:

  • The Rectangle class encapsulates its properties with private variables _width and _height.
class Rectangle {
    private _width: number; // Private property for width
    private _height: number; // Private property for height

    constructor(width: number, height: number) {
        this._width = width; // Initialize width
        this._height = height; // Initialize height
    }
}

Getter for Area:

  • The area property is defined as a getter, allowing it to be accessed like a property while performing a calculation (width × height).
    get area(): number {
        return this._width * this._height; // Calculate and return the area
    }

Setters for Width and Height:

  • Setters for width and height allow controlled updates to these properties. They ensure that only positive values can be assigned.
    set width(value: number) {
        if (value > 0) this._width = value; // Update width if the value is positive
    }

    set height(value: number) {
        if (value > 0) this._height = value; // Update height if the value is positive
    }

Object Instantiation and Method Usage:

  • An instance of Rectangle is created, and the initial area is calculated and logged.

  • The width is updated, demonstrating how the getter recalculates the area based on the new width.

const rect = new Rectangle(10, 20); // Create a Rectangle instance
console.log(rect.area); // Output: 200 (10 * 20)

rect.width = 15; // Update the width
console.log(rect.area); // Output: 300 (15 * 20)

Conclusion

Object-Oriented Programming is one of the most powerful application building paradigms, and TypeScript supports them heavily with solid OOP concepts. Classes, objects, inheritance, encapsulation, polymorphism, abstraction, and generics are what allow developers to write modular, reusable, and type-safe code through accessors. By embracing the overall philosophy expressed in these principles, you can enhance your structure and even maintainability and scalability for your projects using TypeScript.

reference:

https://dev.to/kevin_odongo35/object-oriented-programming-with-typescript-574opeScript

https://dev.to/wizdomtek/exploring-object-oriented-programming-with-typescript-22b

https://www.linkedin.com/pulse/exploring-object-oriented-programming-typescript-examples-rahman-mw4qc/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor