Object Oriented Programming with Typescript

Object Oriented Programming with Typescript

OOP, or Object Oriented Programming, is a powerful programming paradigm that has gained widespread popularity in recent years. At its core, OOP is all about creating software that is modular, flexible, and easy to maintain. Rather than thinking about programs as a series of instructions to be executed one after another, OOP encourages developers to think about programs in terms of the objects that make up their structure.

An object is a self-contained unit of software that encapsulates both data and behavior. This encapsulation is important because it helps to prevent unwanted side effects and makes code easier to understand and maintain. Objects can communicate with one another by sending messages, which typically involve calling methods or accessing properties.

One of the key features of OOP is inheritance, which allows objects to inherit properties and methods from their parent classes. This enables developers to create complex software systems with many different objects that share common functionality.

TypeScript is a popular programming language that extends JavaScript with support for static typing and other advanced features. In TypeScript, OOP is a first-class citizen, with a rich set of language constructs that make it easy to create objects, define classes, and use inheritance and other OOP concepts.

In the rest of this post, we'll explore some of the key concepts and techniques of OOP in TypeScript, and show how you can use them to create elegant, modular, and maintainable code. Let's get started!

What we going to learn in this tutorial

In this tutorial, we will explore how to use OOP concepts in TypeScript. We will cover the following topics:

Encapsulation: Encapsulation is the process of hiding the internal details of an object and exposing only what is necessary. We will learn how to use classes and access modifiers to encapsulate our code and prevent unwanted side effects. Inheritance: Inheritance allows us to create new classes that inherit properties and methods from parent classes. We will learn how to use inheritance to create class hierarchies and reuse code. Polymorphism: Polymorphism allows us to use a single interface to represent multiple types of objects. We will learn how to use interfaces and class inheritance to create polymorphic code. Abstraction: Abstraction is the process of identifying the essential features of an object and ignoring the non-essential details. We will learn how to use abstract classes and interfaces to create abstract code that can be implemented in different ways.

This tutorial is aimed at developers who are new to OOP in TypeScript. I assume that you have a basic understanding of TypeScript syntax and concepts. If you are new to TypeScript, we recommend that you start with a beginner-friendly tutorial on TypeScript, such as Typescript Tutorial. You can also refer to the official documentation at Typescript Documentation for more information on TypeScript.

Let's dive into the four pillars of OOP and learn how to use them in TypeScript!

Why OOP?

Object-oriented programming (OOP) has become one of the most popular programming paradigms due to its many advantages over procedural programming. Here are some of the key reasons why developers prefer OOP:

  • Faster and easier to execute: OOP programs can execute faster and use fewer system resources than procedural programs. This is because OOP programs are optimized for efficient memory allocation and management, which reduces the overhead associated with procedural programming.

  • A clear structure for programs: OOP provides a clear and logical structure for programs, making them easier to understand and maintain. The use of classes and objects helps to organize code into manageable modules, making it easier to identify and fix bugs.

  • DRY principle: OOP promotes the "Don't Repeat Yourself" (DRY) principle, which states that code should not be duplicated unnecessarily. This makes the code more maintainable, as changes only need to be made in one place, rather than in multiple locations.

  • Reusability: OOP makes it easier to create reusable code, as objects can be reused in different parts of the program. This reduces development time and increases code quality, as developers can focus on creating high-quality, reusable code.

  • Flexibility: OOP allows for greater flexibility in programming, as it supports multiple programming styles, including procedural, functional, and event-driven programming. This makes it easier to adapt programs to changing requirements and new technologies.

Overall, OOP is a powerful programming paradigm that offers many benefits for developers. By using OOP concepts in TypeScript, developers can create modular, maintainable, and efficient code that can be easily reused and adapted to meet changing needs.

So far we have learned about the basics of OOP and why it is important. In the next section, we will explore the four pillars of OOP and learn how to use them in TypeScript.

first, let's start with encapsulation.

Encapsulation in TypeScript

Encapsulation is the process of binding the data and the functions that manipulate the data together. In object-oriented programming, it is the practice of hiding the internal workings of an object and exposing only what is necessary for other objects to interact with it. This helps to keep the code organized and secure.

To achieve encapsulation in TypeScript, we use TypeScript modifiers. These modifiers control the visibility of the properties and methods of a class. The three modifiers used for encapsulation in TypeScript are:

  • public: This is the default modifier for TypeScript. Public property or methods can be accessed from anywhere, including outside the class.

  • private: A private property or method can only be accessed from within the class. It is hidden from the outside world, making it inaccessible to other classes.

  • protected: A protected property or method can only be accessed from within the class or its subclasses. It is hidden from the outside world but can be accessed by subclasses of the class.

Let's take a look at an example of encapsulation in TypeScript. In this example, we will create a class called Person that has three private properties: name, age, and address. We will also create three public methods: getName(), getAge(), and getAddress() that will return the value of the corresponding properties. This will allow us to access the properties of the Person class without having direct access to them.

class Person {
  private name: string;
  private age: number;
  private address: string;

  // Constructor function that takes three parameters and initializes the name, age, and address properties of the Person class
  constructor(name: string, age: number, address: string) {
    this.name = name;
    this.age = age;
    this.address = address;
  }

  // Getter function that returns the name property of the Person class
  public getName(): string {
    return this.name;
  }

  // Getter function that returns the age property of the Person class
  public getAge(): number {
    return this.age;
  }

  // Getter function that returns the address property of the Person class
  public getAddress(): string {
    return this.address;
  }
}

// Create an object of the Person class with the name "John", age 25, and address "USA"
let person = new Person(" Clifford", 20, "Ghana");

// Print the name, age, and address properties of the person object to the console
console.log(person.getName()); // Output: Clifford
console.log(person.getAge()); //  Output: 25
console.log(person.getAddress()); // Output: Ghana

In this example, we have created a class Person with three private properties name, age, and address. We have also created a constructor to initialize the properties of the class. We have created three public methods getName(), getAge(), and getAddress() to access the private properties of the class. We have created an object of the Person class and called the public methods to access the private properties of the class.

Encapsulation is one of the most important concepts of OOP. It helps us to create robust, secure, maintainable, and reusable code. By hiding the internal workings of an object, we can ensure that the object is used correctly and cannot be tampered with from outside the class.

so now we have learned about encapsulation in typescript. let's move on to inheritance.

Inheritance in TypeScript

Inheritance is the process of creating new classes from existing classes. The new classes are called subclasses, and the existing classes are called superclasses. Subclasses inherit the properties and methods of their parent classes, which makes it easier to reuse code and create modular programs.

In TypeScript, we can use the extends keyword to create a subclass from an existing class. The subclass inherits all the properties and methods of the superclass, and can also add its own properties and methods. The subclass can also override the properties and methods of the superclass.

Let's take a look at an example of inheritance in TypeScript. In this example, we will create a class called Person that has three properties: name, age, and address. We will then create a subclass called Student that extends the Person class. The Student class has an additional property roll, which represents the student's roll number. The Student class also has a method getDetails() that returns the details of the student. The Student class overrides the getName() method of the Person class to return the student's name and roll number.

// Define the Person class with three properties: name, age, and address
class Person {
  protected name: string;
  protected age: number;
  protected address: string;

  // Constructor function that takes three parameters and initializes the name, age, and address properties of the Person class
  constructor(name: string, age: number, address: string ,phone : number, school: string) {
    this.name = name;
    this.age = age;
    this.address = address;
  }

  // Getter function that returns the name property of the Person class

  public getName(): string {
    return this.name;
  }

  // Getter function that returns the age property of the Person class
  public getAge(): number {
    return this.age;
  }

  // Getter function that returns the address property of the Person class
  public getAddress(): string {
    return this.address;
  }
}

// Define the Student class as a subclass of Person, with an additional property rollNo, phone  and school
class Student extends Person {
  private rollNo: number;

  constructor(name: string, age: number, address: string, rollNo: number , phone: number, school: string) {
    super(name, age, address);
    this.rollNo = rollNo;
    this.phone = phone;
    this.school = school;
  }

  // Override the getName() method of Person to return the student's name and roll number
  public getName(): string {
    return `${this.name} your roll number is  ${this.rollNo}`;
  }

  // Define a method that returns the details of the student
  public getDetails(): string {
    return `Name: ${this.name}, Age: ${this.age}, Address: ${this.address}, Roll No: ${this.rollNo} , phone: ${this.phone} , school: ${this.school}`;
  }
}

// Create an object of the Student class
let student = new Student("Clifford", 25, "Ghana", 10, 0240000000, "AAMUSTED");

console.log(student.getName()); // Output: clifford your roll number is 10
console.log(student.getAge()); //  Output: 25
console.log(student.getAddress()); // Output: Ghana
console.log(student.getDetails()); // Output: Name: Clifford, Age: 25, Address: Ghana, Roll No: 10 , phone: 0240000000 , school: AAMUSTED

In this example, we have created a class Person with three properties: name, age, and address. We have also created a constructor to initialize the properties of the class. We have created a subclass Student that extends the Person class. The Student class has an additional property rollNo, phone, and school. The Student class also has a method getDetails() that returns the details of the student. The Student class overrides the getName() method of the Person class to return the student's name and roll number.

Now let's take at a polymorphism in typescript.

Polymorphism in TypeScript

Polymorphism is an important concept in object-oriented programming (OOP) that allows objects to take on multiple forms. In TypeScript, polymorphism can be achieved through interfaces, abstract classes, and generics.

Interfaces

An interface is a TypeScript construct that defines the structure of an object. It can be used to define the properties and methods of an object, as well as the structure of a function. Interfaces are commonly used to enforce a certain structure for objects that are expected to have a similar shape. Here's an example:


// Define an interface Person with two properties: name and age
interface Person {
  name: string;
  age: number;
}

// Define a function that takes an object of type Person as a parameter and returns a string
function getDetails(person: Person): string {
  return `Name: ${person.name}, Age: ${person.age}`;
}

// Create an object of type Person
let person = { name: "John", age: 25 };
console.log(getDetails(person)); // Output: Name: John, Age: 25

In the example above, we define an interface Person with two properties: name and age. We also define a function getDetails that takes an object of type Person as a parameter and returns a string. We create an object person of type Person and pass it to the getDetails function. The function uses the properties of the person object to return a string that contains the person's name and age.

Abstract Classes

An abstract class is a class that cannot be instantiated directly. Instead, it serves as a base for other classes to inherit from. Abstract classes can define abstract methods that must be implemented by the inheriting classes. Abstract classes are useful for creating a common base for a group of related classes. Here's an example:

// Define an abstract class Animal with an abstract method makeSound and a concrete method move
abstract class Animal {
  abstract makeSound(): void;

//  concrete method
  move(): void {
    console.log("Moving...");
  }
}

// Define a class Dog that extends the Animal class and implements the makeSound method
class Dog extends Animal {
  makeSound(): void {
    console.log("Woof!");
  }
}

// Create an instance of the Dog class and call its makeSound and move methods
let dog = new Dog();
dog.makeSound(); // Output: Woof!
dog.move(); // Output: Moving...

In the example above, we define an abstract class Animal that has an abstract method makeSound and a concrete method move. We create a class Dog that extends Animal and implements the makeSound method. We create an instance of Dog and call its makeSound and move methods.

Generics

Generics are a TypeScript feature that allows us to write code that works with a variety of data types. We can define a generic type that is a placeholder for a specific data type. Generics are commonly used in data structures like arrays and lists. Here's an example


// Define a generic function reverse that takes an array of type T and returns an array of type T
function reverse<T>(list: T[]): T[] {
  return list.reverse();
}

// Create two arrays of type string and type number and pass them to the reverse function
let names = ["John", "Mary", "Joe"];
let reversedNames = reverse(names);
console.log(reversedNames); // Output: ["Joe", "Mary", "John"]

// Create two arrays of type string and type number and pass them to the reverse function
let numbers = [1, 2, 3, 4, 5];
let reversedNumbers = reverse(numbers);
console.log(reversedNumbers); // Output: [5, 4, 3, 2, 1]

In the example above, we define a function reverse that takes an array of type T and returns an array of type T. We create two arrays, one of type string and one of type number, and pass them to the reverse function. The function reverses the order of the elements in each array and returns the reversed array.

In summary, polymorphism in TypeScript allows us to write code that is more flexible and reusable. Interfaces, abstract classes, and generics are powerful.

Now let's dive into the last pillar of OOP which is Abstraction,

Abstraction in TypeScript

Abstraction is an essential concept in object-oriented programming that involves hiding the implementation details of an object from the user. Instead of exposing the inner workings of an object, abstraction provides a clear and simplified view of its functionality.

In TypeScript, abstraction can be achieved using abstract classes or interfaces. Abstract classes are classes that cannot be instantiated, and they contain one or more abstract methods that must be implemented by their child classes. Interfaces are similar to abstract classes, but they only define the structure of a class and do not contain any implementations.

To illustrate abstraction in TypeScript, let's define an abstract class Person with private properties name, age, and address. The Person class also has three public methods getName(), getAge(), and getAddress(), which provide access to the private properties of the class. Additionally, the Person class contains an abstract method getDetails(), which must be implemented by any child class.

We then define a child class Student that extends the Person class. The Student class has an additional private property roll, which represents the student's roll number. The Student class also implements the getDetails() method defined in the Person class, which returns a string containing the student's name, age, address, and roll number.

Finally, we create an object of the Student class and call the getDetails() method to retrieve the student's information.

Here is the code for the example:

// This is an abstract class called Person that contains private properties name, age, and address,
// and methods getName(), getAge(), and getAddress(). It also contains an abstract method getDetails(),
// which is implemented by its subclass Student.

abstract class Person {
  private name: string;
  private age: number;
  private address: string;

  constructor(name: string, age: number, address: string) {
    this.name = name;
    this.age = age;
    this.address = address;
  }

  public getName(): string {
    return this.name;
  }

  public getAge(): number {
    return this.age;
  }

  public getAddress(): string {
    return this.address;
  }

  abstract getDetails(): string;
}

// This is a subclass called Student that extends the Person class.
// It contains a private property rollNo and methods getRollNo() and getDetails(),
// which implement the abstract method getDetails() of the Person class.

class Student extends Person {
  private rollNo: number;

  constructor(name: string, age: number, address: string, rollNo: number) {
    super(name, age, address);
    this.rollNo = rollNo;
  }      

  public getRollNo(): number {
    return this.rollNo;
  }

  public getDetails(): string {
    return `${this.getName()} ${this.getAge()} ${this.getAddress()} ${this.getRollNo()}`;
  }
}

// This creates a new object of the Student class and calls its getDetails() method.

let student = new Student("John", 25, "USA", 1);
console.log(student.getDetails()); // Output: John 25 USA 1

In this example, we have created an abstract class Person with three private properties name, age, and address. The Person class also has three public methods getName(), getAge(), and getAddress(), which provide access to the private properties of the class. Additionally, the Person class contains an abstract method getDetails(), which must be implemented by any child class.

Thank you for reading you can also check my GitHub for more examples of OOP in TypeScript oop-typescript