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 toname
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 theAnimal
class, inheriting its properties and methods.The
super(name)
call initializes thename
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 themyDog
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 theShape
interface.It has private properties
width
andheight
, 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 theShape
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
andCircle
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 theVehicle
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 themyCar
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 acalculateArea()
method that returns a number.The
Color
interface defines acolor
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 theShape
andColor
interfaces.It has private properties
width
andheight
, and a public propertycolor
.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 ofShape
andColor
.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
andheight
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://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor