Published 2023-06-06 12:55:07
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 modifiers – public, 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:
- Basic programming knowledge: This blog post assumes that you already have some familiarity with programming concepts in general.
-
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.
-
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