Only registred users can make comments

Elevating Your TypeScript: Exploring Classes, Inheritance And Access Modifiers

Spacecraft of code, merging cosmos and coding ❤️

TL;DR 

In this post, we'll navigate the fundamentals of TypeScript, emphasizing the capabilities of classes and the concept of inheritance.

We'll take a hands-on approach, starting with the creation of a Vehicle class and extending it to construct a Car class.

Prepare as we explore the functionalities of getters, setters, and TypeScript access modifiers: public, private, and protected.

Content

Requirements
Recap of Object-Oriented Programming (OOP)
Diving into TypeScript Classes and Inheritance
super() keyword
setter()
getter()
Access Modifiers - public, private and protected
The Full Code of this tutorial
Summary

Why TypeScript on devoriales.com

You might wonder why I sometimes write TypeScript articles even if devoriales.com is primarly focusing on Python. 

The reason is, as a developer who works extensively with AWS CDK for managing solution implementations on AWS, my daily activities revolve around crafting robust and scalable architectures in the cloud. I navigate the AWS ecosystem, and the current Infrastructure As Code (IaC) that I work with is based on AWS CDK based on TypeScript.

Motivated by my own experiences and driven by a passion for knowledge sharing, I find great joy in disseminating what I've learned. Through my articles, I aim to empower fellow developers, AWS enthusiasts, and anyone embarking on their AWS CDK journey with the invaluable insights I've gained. 

An Introduction To A small TypeScript Project

TypeScript, a statically typed superset of JavaScript, provides optional static typing and advanced object-oriented programming features, making it an attractive language for building scalable applications. At its heart, TypeScript offers enhanced code quality and predictability. If you're already familiar with JavaScript, this is pretty easy.

In this blog post, we will build small project that will serve as an excellent learning opportunity to understand TypeScript classes, inheritance, and access modifiers. By constructing this project, we'll gain practical experience in creating class structures and leveraging the power of object-oriented programming.

Our project will begin with defining a base class, where we will define essential components of a class, including the constructor, attributes, and methods. We'll learn how to define and initialize class attributes and explore the significance of the constructor function.

As we progress, we will extend our project by creating another class that inherits attributes and methods from the base class. Through this process, we'll learn the power of inheritance, allowing us to reuse and extend functionality from existing classes.

We will learn the access modifierspublic, private, and protected. These access modifiers provide control over the visibility and accessibility of class members, ensuring data encapsulation and maintaining the integrity of our code.

The following UML diagram shows the classes that we'll write in this small project:

 the +denotes public, - denotes private and #denotes protected.

The project revolves around building a Vehicle Management System that utilizes TypeScript classes and inheritance. At the core of the system is the Vehicle class, which represents the base class for all vehicles. It has attributes such as color, year, drive, and price, with varying levels of visibility - public, private, and protected.

The Vehicle class also has a protected method called drive(), which can be overridden by subclasses. This method provides a basic implementation of the driving behavior.

One of the subclasses derived from the Vehicle class is the Car class. It inherits attributes and methods from the Vehicle class and adds additional attributes like name, mode, wheels, horsePower, and carPrice. The Car class overrides the drive() method inherited from the Vehicle class, providing a specialized implementation that includes the car's specific details.

The Car class also introduces additional methods, such as setPrice() and getPrice(), which allow for setting and retrieving the car's price.

Requirements

Before we start, make sure you have the following installed on your development environment:

  1. Basic programming knowledge: This blog post assumes that you already have some familiarity with programming concepts in general.
  2. Node.js: Node.js is a runtime environment which allows us to run JavaScript code on the server side. Node.js can be downloaded and installed from the official website.

  3. TypeScript: Once Node.js is installed, you can install TypeScript globally on your machine using the Node Package Manager (npm), which is bundled with Node.js. Open your terminal or command prompt and run the following command:

    npm install -g typescript

A Brief Recap of Object-Oriented Programming (OOP)

Object-Oriented Programming (OOP) is a programming paradigm that's based on the concept of "objects", which can contain data and code—data in the form of fields (also known as properties or attributes), and code in the form of procedures (also known as methods). The principle of OOP includes concepts like classes, inheritance, encapsulation, polymorphism, and abstraction.

Let's consider a simple JavaScript example using a Vehicle class to highlight some of these key concepts:

class Vehicle {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }
    
    startEngine() {
        console.log(`The ${this.make} ${this.model}'s engine is starting... Vroom!`);
    }
}

const myVehicle = new Vehicle('Porsche', '911');
myVehicle.startEngine();

In this example, Vehicle is a class, and myVehicle is an instance (or object) of that class. The Vehicle class has a constructor, which is a special method that initializes new objects. It also has a method called startEngine(). This example encapsulates the core principles of OOP—creating modular, reusable code through the use of classes and objects.

The output of the code is:

The Porsche 911's engine is starting... Vroom!

Diving into TypeScript Classes and Inheritance

TypeScript classes are the core components for creating reusable code. They encapsulate data for the object creation and provide a blueprint for objects.

The following code is a simple example of a Vehicle class:

class Vehicle {
    constructor(public color: string, private year: number, drive: string, price: number) {}
    
    protected drive(): string {
        return 'driving';
    }
}

In the above class, constructor is a special method for creating and initializing objects created within a class. The public, private and protected keywords are access modifiers, and they control the visibility of the properties and methods within a class:

  • public: It's accessible from anywhere, even outside of the class.
  • private: It's only accessible within the class that declares it.
  • protected: It's accessible within the class that declares it and in its subclasses.

Next, we'll create a Car class, a subclass of the Vehicle class, demonstrating the concept of inheritance:

class Car extends Vehicle {
    constructor(public name: string, public model: string, public wheels: number, color: string, year: number, drive: string, public horsePower: number, public price: number) {
        super(color, year, drive, price);
    }
    drive(): string {
        const drive = super.drive();
        console.log(drive);
        return `I'm driving a ${this.name} ${this.model} that has ${this.horsePower} horse power`;
    }

    set setPrice(price: number) {
        this.price = price;
    }

    get getPrice(): number {
        console.log(`The price is $${this.price}`);
        return this.price;
    }
}

The Car class we've created extends the Vehicle class, which means it inherits all properties and methods from Vehicle. In the constructor, we've defined several new properties, and with the super() function, you're calling the constructor of the parent class (Vehicle).

The drive method in Car is overriding the inherited drive method from Vehicle. It's first calling super.drive() to execute the original drive method from Vehicle, then it adds additional functionality (printing a specific message).

super() keyword

When a subclass extends a parent class, it inherits all the attributes and methods defined in the parent class. However, there might be scenarios where the subclass needs to modify or extend the behavior inherited from the parent class while still retaining some of the parent's functionality.

In TypeScript (as well as in many other object-oriented programming languages), the super keyword is used to refer to the parent class or superclass. It provides a way to access and invoke the members (attributes or methods) of the parent class within a subclass.

This is where the super keyword becomes valuable. It allows the subclass to call the parent class's constructor, access its attributes, or invoke its methods. By using super, we can perform additional operations specific to the subclass, while still leveraging the existing functionality from the parent class.

In the context of constructors, the super keyword is used to call the constructor of the parent class. This ensures that the initialization code defined in the parent class's constructor is executed before the subclass's constructor code. This way, we can set up the inherited attributes and establish the base behavior before customizing it further in the subclass.

Additionally, the super keyword can also be used to access overridden methods from the parent class. By invoking the parent class's method using super, we can reuse and extend the functionality defined in the parent class while providing specialized behavior in the subclass.

Next, we have setPrice and getPrice, which are setter and getter methods, respectively.

Setter: setPrice

A setter is a method that allows us to set the value of a certain property. Here's what the setPrice setter is doing:

set setPrice(price: number) {
    this.price = price;
}

With this method, you can easily change the price of a Car instance. For example:

const car = new Car("BMW", "X5", 4, "red", 2020, "driving", 300, 50000);
car.setPrice = 60000; // Update the price

Getter: getPrice

On the other hand, a getter is a method that gets the value of a certain property. Here's the getPrice getter:

get getPrice(): number {
    console.log(`The price is $${this.price}`);
    return this.price;
}

With this method, you can retrieve the current price of a Car instance and it will also log the price to the console. For instance:

console.log(car.getPrice); // Logs: "The price is $60000" and returns 60000

By using setters and getters, you can control how properties in your classes are accessed and modified, which helps keep your data safe and consistent.

Access Modifiers - public, private and protected

In TypeScript, each member (properties or methods) of a class has an access modifier which governs its visibility to the rest of the code.

The three types of access modifiers are: public, private, and protected.

Public

In our code, public access modifier is used in the Vehicle and Car class constructors. The members such as color, name, model, wheels, horsePower, and price are declared as public, meaning they can be accessed and modified from anywhere, inside or outside of the classes.

For instance, when creating a new instance of Car:

const vehicle1 = new Car("BMW", "X5", 4, "red", 2020, "driving", 300, 50000);

You're able to access the public property price and change it using the setPrice setter:

vehicle1.setPrice = 30000;

Private

The private access modifier is used for the year property in the Vehicle class. This means year can only be accessed and modified within the Vehicle class itself. It's not accessible outside of this class or in the Car class which extends Vehicle.

Note: The year property isn't used later in the code, so you won't see a direct application of private here. But if you were to try to access or modify year from an instance of Vehicle or Car, you'd encounter an error.

You could try to access it like:

vehicle1.year // this will not work because it is private error: Property 'year' is private and only accessible within class 'Vehicle'.

Protected

The protected access modifier is used for the drive method in the Vehicle class. This means the drive method can be called within the Vehicle class and also within the Car class that extends Vehicle.

You can see this in action here:

drive(): string {
    const drive = super.drive()
    console.log(drive);
    return `I\'m driving a ${this.name} ${this.model} that has ${this.horsePower} horse power`;
}

In the Car class, the drive method calls the drive method from the Vehicle class using super.drive(). This is allowed because drive in Vehicle is protected. If it was private, you wouldn't be able to access it in Car.

This sums up how the public, private, and protected access modifiers are used in your code. They play a crucial role in controlling access to class properties and methods, promoting effective encapsulation in your TypeScript applications.

The Full Code

The following is full working code that we've written in this blog post. Play around with it and evolve it to have more attributes, methods and logic.

// Base Class
class Vehicle {
    constructor(public color: string, private year: number, protected drive: string, protected price: number) {
    }

    // protected method
    protected drive(): string {
        return 'driving';
    }
}

// Derived Class
class Car extends Vehicle {
    // Constructor for derived class must contain a 'super' call
    constructor(public name: string, public model: string, public wheels: number, color: string, year: number, drive: string, public horsePower: number, public carPrice: number) {
        super(color, year, drive, carPrice);
    }

    // Overriding method from base class
    drive(): string {
        const drive = super.drive();
        console.log(drive);
        return `I'm driving a ${this.name} ${this.model} that has ${this.horsePower} horse power`;
    }

    // Setter for price
    set setPrice(price: number) {
        this.carPrice = price;
    }

    // Getter for price
    get getPrice(): number {
        console.log(`The price is $${this.carPrice}`);
        return this.carPrice;
    }
}

// Instance of Car class
const vehicle1 = new Car("BMW", "X5", 4, "red", 2020, "driving", 300, 50000);
console.log(vehicle1.drive());

// Set price using setter
vehicle1.setPrice = 30000;

// Get price using getter
console.log(vehicle1.getPrice);

Summary

We've covered TypeScript classes, OOP concepts, and the use of keywords like public, private, protected, get, set, and super.

The Vehicle and Car classes we've created serve as an excellent base for exploring more advanced TypeScript features.

Remember, good programming is all about building upon basics, so take your time, explore, experiment, and build!

Keep coding and stay tuned!

About the Author

Aleksandro Matejic, a Cloud Architect, began working in the IT industry over 21 years ago as a technical specialist, right after his studies. Since then, he has worked in various companies and industries in various system engineer and IT architect roles. He currently works on designing Cloud solutions, Kubernetes, and other DevOps technologies.

In his spare time, Aleksandro works on different development projects such as developing devoriales.com, a blog and learning platform launching in 2022/2023. In addition, he likes to read and write technical articles about software development and DevOps methods and tools.

You can contact Aleksandro by visiting his LinkedIn Profile

Comments