Master OOP Basics: Step-by-Step Code Examples for Beginners

Object-Oriented Programming (OOP) is a powerful paradigm that allows developers to model real-world entities using objects and classes. This approach helps in creating modular, scalable, and maintainable code. In this comprehensive guide, we will delve into the fundamental concepts of OOP using TypeScript examples. By the end of this article, you will have a thorough understanding of classes, objects, inheritance, polymorphism, encapsulation, and abstraction in TypeScript.

Introduction to Object-Oriented Programming

OOP is a programming paradigm centered around objects rather than actions. It allows us to bundle data and methods that operate on that data into a single unit called an object. This encapsulation fosters modularity and reusability.

What is TypeScript?

TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. It brings type-safety to JavaScript, allowing for early error detection and improved development experience.

Classes and Objects

Defining a Class

A class is a blueprint for creating objects. It defines properties and methods that the created objects will have. In TypeScript, a class is defined using the class keyword.

class Person {
    name: string;
    age: number;

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

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

In the example above, we define a Person class with two properties: name and age. The constructor initializes these properties, and the greet method outputs a greeting message.

Creating an Object

An object is an instance of a class. We create an object by using the new keyword followed by the class name.

const person1 = new Person('Alice', 30);
person1.greet(); // Output: Hello, my name is Alice and I am 30 years old.

Inheritance

Inheritance is a mechanism where a new class (derived class) inherits the properties and methods of an existing class (base class). This promotes code reuse and establishes a relationship between classes.

Base and Derived Classes

In TypeScript, we use the extends keyword to create a derived class.

class Employee extends Person {
    employeeId: number;

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

    displayEmployeeInfo() {
        console.log(`Employee ID: ${this.employeeId}, Name: ${this.name}, Age: ${this.age}`);
    }
}

const employee1 = new Employee('Bob', 25, 1234);
employee1.displayEmployeeInfo(); // Output: Employee ID: 1234, Name: Bob, Age: 25

In this example, the Employee class inherits from the Person class. It adds a new property employeeId and a method displayEmployeeInfo. The super keyword calls the constructor of the base class.

Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It supports method overriding, where a derived class provides a specific implementation of a method already defined in its base class.

Method Overriding

In TypeScript, we override a method by defining it in the derived class with the same name as in the base class.

class Manager extends Employee {
    department: string;

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

    displayEmployeeInfo() {
        console.log(`Manager ID: ${this.employeeId}, Name: ${this.name}, Age: ${this.age}, Department: ${this.department}`);
    }
}

const manager1 = new Manager('Carol', 40, 5678, 'HR');
manager1.displayEmployeeInfo(); // Output: Manager ID: 5678, Name: Carol, Age: 40, Department: HR

Here, the Manager class overrides the displayEmployeeInfo method to include additional information specific to managers.

Encapsulation

Encapsulation is the practice of bundling data and methods that operate on that data within a class, restricting direct access to some of the class’s components. This is achieved through access modifiers: public, private, and protected.

Access Modifiers

  • public: Members are accessible from anywhere.
  • private: Members are accessible only within the class.
  • protected: Members are accessible within the class and its subclasses.
class BankAccount {
    private balance: number;

    constructor(initialBalance: number) {
        this.balance = initialBalance;
    }

    deposit(amount: number) {
        this.balance += amount;
    }

    withdraw(amount: number) {
        if (amount <= this.balance) {
            this.balance -= amount;
        } else {
            console.log('Insufficient balance');
        }
    }

    getBalance() {
        return this.balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // Output: 1300

In this example, the balance property is private, and can only be modified through the deposit and withdraw methods. This ensures controlled access to the account balance.

Abstraction

Abstraction is the concept of hiding complex implementation details and showing only the essential features of an object. It is achieved through abstract classes and interfaces.

Abstract Classes

An abstract class cannot be instantiated and is meant to be subclassed. It can contain abstract methods that must be implemented by derived classes.

abstract class Shape {
    abstract calculateArea(): number;

    displayArea() {
        console.log(`The area is ${this.calculateArea()}`);
    }
}

class Circle extends Shape {
    radius: number;

    constructor(radius: number) {
        super();
        this.radius = radius;
    }

    calculateArea(): number {
        return Math.PI * Math.pow(this.radius, 2);
    }
}

const circle = new Circle(5);
circle.displayArea(); // Output: The area is 78.53981633974483

In this example, the Shape class is abstract and contains an abstract method calculateArea. The Circle class extends Shape and provides an implementation for calculateArea.

Interfaces

Interfaces define the structure that a class must adhere to. They cannot contain implementation details.

interface Vehicle {
    make: string;
    model: string;
    startEngine(): void;
    stopEngine(): void;
}

class Car implements Vehicle {
    make: string;
    model: string;

    constructor(make: string, model: string) {
        this.make = make;
        this.model = model;
    }

    startEngine() {
        console.log(`Starting engine of ${this.make} ${this.model}`);
    }

    stopEngine() {
        console.log(`Stopping engine of ${this.make} ${this.model}`);
    }
}

const myCar = new Car('Toyota', 'Corolla');
myCar.startEngine(); // Output: Starting engine of Toyota Corolla
myCar.stopEngine(); // Output: Stopping engine of Toyota Corolla

Here, the Vehicle interface defines the structure for a vehicle, and the Car class implements this interface, ensuring it adheres to the defined structure.

Advanced OOP Concepts

Static Members

Static members belong to the class itself rather than any instance of the class. They are accessed using the class name.

class MathUtils {
    static PI: number = 3.14159;

    static calculateCircumference(radius: number): number {
        return 2 * MathUtils.PI * radius;
    }
}

console.log(MathUtils.PI); // Output: 3.14159
console.log(MathUtils.calculateCircumference(5)); // Output: 31.4159

Getters and Setters

Getters and setters allow controlled access to the properties of a class.

class Student {
    private _name: string;

    constructor(name: string) {
        this._name = name;
    }

    get name(): string {
        return this._name;
    }

    set name(newName: string) {
        if (newName.length > 0) {
            this._name = newName;
        } else {
            console.log('Name cannot be empty');
        }
    }
}

const student = new Student('John');
console.log(student.name); // Output: John
student.name = 'Doe';
console.log(student.name); // Output: Doe
student.name = ''; // Output: Name cannot be empty

Conclusion

Object-Oriented Programming in TypeScript provides a robust framework for building scalable and maintainable applications. By understanding and applying the principles of classes, objects, inheritance, polymorphism, encapsulation, and abstraction, developers can create well-structured and efficient code. TypeScript enhances this paradigm with type safety, making it a preferred choice for many developers.

We hope this comprehensive guide has given you a clear understanding of the basic and advanced concepts of OOP in TypeScript. By mastering these concepts, you will be well-equipped to tackle complex programming challenges and develop high-quality software solutions.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top
Copy link
Powered by Social Snap