Working With Classes
If you are used to working with object-oriented programming,
chances are you are already familiar with classes and you can
skip this section. But if you are not or need a little reminder,
continue reading below.
What is a class?
A class is a blueprint for creating objects that share the same
properties and methods. It defines the structure of an object,
including its data fields (attributes) and the functions that
operate on those fields (methods).
It is a way of organizing and structuring code, and it provides a way to create objects that have a common structure and behavior. Classes can be used to model real-world entities, such as cars, animals, or people, and they can be used to create abstract concepts, such as a vector or a linked list.
It is a way of organizing and structuring code, and it provides a way to create objects that have a common structure and behavior. Classes can be used to model real-world entities, such as cars, animals, or people, and they can be used to create abstract concepts, such as a vector or a linked list.
Defining a class
You can create a class using the
class
keyword, followed by
the name of the class. Here is an example of a simple Person
class:class Person {
// class body
}
The class body is where we define the attributes and methods of the class.
For example, we could define a
name
attribute and a greet()
method like this:class Person {
name: string;
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
The
name
attribute is a string, and the greet()
method prints a greeting
message to the console. Note that we use the this
keyword to refer to the
current object’s attributes and methods.The constructor
The
constructor
function is a special function that is called when an object
is created from a class. It is used to initialize the object’s data fields and
run any bootstrapping logic.
Here is an example of a Person
class with a constructor
function:class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
The
constructor
function takes a name
argument and assigns it to the name
attribute of the object. This ensures that each Person
object has a name
attribute that is set to the correct value.Creating objects
Once we have defined a class, we can create objects from that class using the
new
keyword. Here is an example of creating a Person
object and calling its
greet()
method:let person = new Person("John");
person.greet(); // prints "Hello, my name is John"
In this example, we create a
Person
object and assign it to the person
variable.
We pass the "John"
string as an argument to the constructor
function, which sets
the name
attribute of the object. Then we call the object’s greet()
method,
which prints a greeting message to the console.Inheritance
Inheritance is a fundamental concept in object-oriented programming (OOP)
that allows one class to inherit the attributes and methods of another class.
In TypeScript, inheritance is implemented using the
extends
keyword.The idea behind inheritance is to create a hierarchy of classes,
where a subclass can inherit the attributes and methods of its superclass.
This allows us to reuse code and avoid duplication. For example, we could
define a
Here is an example of how we could define a
Vehicle
class that has a drive()
method, and then create
subclasses for specific types of vehicles, such as Car
and Truck
,
which inherit the drive()
method from the Vehicle
class.Here is an example of how we could define a
Vehicle
class and a Car
subclass that inherits from it:class Vehicle {
drive() {
console.log("Driving...");
}
}
class Car extends Vehicle {
// additional car-specific methods and attributes
}
In this example, the
Car
class inherits the drive()
method from the
Vehicle
class. This means that we can create a Car
object and call
its drive()
method, and it will print “Driving…” to the console.Inheritance allows us to create subclasses that are specialized versions
of their superclass. For example, the
You can use the
Car
class could have additional
methods and attributes that are specific to cars, such as a honk()
method
or a numDoors
attribute. These attributes and methods would be in
addition to the ones that it inherits from the Vehicle
class.You can use the
super
keyword to call the constructor of the
superclass from within a subclass. This is often used to pass
arguments to the superclass’s constructor and initialize the
object’s data fields. Here is an example of using the super
keyword to call the Vehicle
class’s constructor from within
the Car
class:class Vehicle {
numWheels: number;
constructor(numWheels: number) {
this.numWheels = numWheels;
}
drive() {
console.log("Driving...");
}
}
class Car extends Vehicle {
numDoors: number;
constructor(numDoors: number) {
super(4); // call the Vehicle class's constructor
this.numDoors = numDoors;
}
// additional car-specific methods and attributes
}
In this example, the
Car
class’s constructor calls the
Vehicle
class’s constructor using the super
keyword.
This passes the 4
argument to the Vehicle
class’s
constructor, which sets the numWheels
attribute of
the object to 4
. The Car
class’s constructor
then sets the numDoors
attribute of the object.Encapsulation
Encapsulation refers to the mechanism of restricting
access to the internal properties and methods of a class
from outside it. To achieve that you will need to use
accessibility modifiers.
In TypeScript, the accessibility modifiers are
public
, private
, and protected
.
These keywords determine whether a class member can be accessed from
outside the class or from within derived classes (subclasses).public
Class members that are marked as
public
can be accessed from anywhere,
both inside and outside the class where they are defined. Class members
that do not have an explicit accessibility modifier are considered public
by default.Here is an example of a
Person
class with a public
attribute:class Person {
public name: string;
constructor(name: string) {
this.name = name;
}
}
In this example, the
name
attribute is public
, so it can
be accessed from anywhere. We can create a Person
object and
access its name
attribute like this:let person = new Person("John");
console.log(person.name); // prints "John"
It is a good practice to make it public only what should actually
be acessible externally. For internal behaviour of your objects
you should use one of the next two accessibility modifiers we are
going to see here.
private
The
private
modifier restricts the accessibility of a class member
to within the class in which it is defined. This means that private
properties or methods cannot be accessed from outside the class or
from derived classes.Here’s an example:
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
public sayHello() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const john = new Person('John');
john.sayHello(); // Output: "Hello, my name is John."
console.log(john.name); // Compilation error: Property 'name' is private and only accessible within class 'Person'.
In this example, the
Person
class has a private name
property
which can only be accessed within the Person
class. The constructor
of the Person
class initializes the name property with the value
passed to it.The
sayHello
method is declared as a public method, which means it
can be accessed from outside the class. The sayHello
method uses
the name property to log a message to the console. Remember that
methods and properties are public by default, so the public
keyword
can be ommited.When we create a new
Person
instance and call the sayHello
method,
it logs the message to the console with the value of the name property.
However, if we try to access the name
property directly from outside
the class (as shown in the console.log statement), we will get a
compilation error, because the name
property is private and can only
be accessed within the Person
class.protected
The
protected
modifier is similar to private, in that it restricts the
accessibility of a class member. However, protected members can be accessed
within the class in which they are defined and within any classes that inherit
from that class. This means that protected members can be accessed by the class
itself and by any derived classes, but not by code outside of the class hierarchy.Here’s an example:
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
protected sayName() {
console.log(`My name is ${this.name}.`);
}
}
class Employee extends Person {
private id: number;
constructor(name: string, id: number) {
super(name);
this.id = id;
}
public introduce() {
this.sayName();
console.log(`I am an employee with ID ${this.id}.`);
}
}
const john = new Employee('John', 123);
john.introduce(); // Output: "My name is John." followed by "I am an employee with ID 123."
console.log(john.name); // Compilation error: Property 'name' is protected and only accessible within class 'Person' and its subclasses.
john.sayName(); // Compilation error: Property 'sayName' is protected and only accessible within class 'Person' and its subclasses.
In this example, we have a
Person
class with a protected name
property and a protected sayName
method. The Employee class
extends Person
and adds a private id
property and a public
introduce
method that calls the sayName method from the Person
class.We can create an
Employee
instance and call its introduce
method,
which calls the sayName
method from the Person
class. These methods
are accessible from the Employee
class because it inherits from Person
.However, if we try to access the
name
property or call the sayName
method
directly from the Employee
instance, we will get a compilation error, because
these members are protected and can only be accessed within the Person
class
and its subclasses.Abstract classes and methods
An abstract class is a class that cannot be instantiated directly,
but can only be used as a base class for other classes. An abstract
class can contain both abstract and non-abstract methods and properties.
An abstract method is a method that is declared but does not provide an
implementation. Abstract methods are intended to be implemented by the
derived classes, which inherit from the abstract class. Abstract methods
are declared using the
abstract
keyword, and must be implemented in the
derived class using the override
keyword. The override
keyword might
not be neccesary, depending on your typescript configuration.Abstract classes are often used to define a common interface or behavior
that can be shared by multiple derived classes. By defining an abstract class,
developers can ensure that the derived classes adhere to a common contract,
while still allowing the derived classes to implement their own specific
behavior.
Here is an example of an abstract class in TypeScript:
abstract class Person {
protected name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
abstract sayHello(): void;
walk(distanceInMeters: number = 0) {
console.log(`${this.name} walked ${distanceInMeters}m.`);
}
}
class Employee extends Person {
private id: number;
constructor(name: string, age: number, id: number) {
super(name, age);
this.id = id;
}
override sayHello() {
console.log(`Hello, my name is ${this.name} and my employee ID is ${this.id}.`);
}
}
In this example,
Person
is an abstract class that defines a name
property, an age
property, a walk
method, and an abstract sayHello
method. Employee
is a concrete
class that extends Person
and adds a new id
property. Employee
overrides the
sayHello
method inherited from Person
with its own implementation.Here’s how you could use the
Employee
class:const employee = new Employee("John Doe", 30, 12345);
employee.sayHello(); // Output: "Hello, my name is John Doe and my employee ID is 12345."
employee.walk(100); // Output: "John Doe walked 100m."
This is not all you can do with classes, but what was
explained so far should be enough to follow what is to come.