intro-frontend-course

Lecture 1: Object-Oriented Programming

Introduction to OOP

Object-Oriented Programming (OOP) is a programming paradigm centered around objects rather than functions and logic. OOP is crucial in JavaScript because it allows developers to create modular, reusable code. The four fundamental principles of OOP are:

  1. Encapsulation: Bundling data and methods that operate on that data within a single unit (object).
  2. Inheritance: Mechanism by which one class can inherit properties and methods from another.
  3. Polymorphism: Ability to process objects differently based on their data type or class.
  4. Abstraction: Hiding complex implementation details and showing only the necessary features of an object.

Real-World Scenario: Online Shopping Platform

Imagine you are building an online shopping platform. You need to manage users, products, and orders. OOP allows you to structure your code in a way that each of these entities (users, products, orders) can be represented by objects, encapsulating their properties and behaviors.

Classes and Objects

Classes are blueprints for creating objects (instances). They encapsulate data and methods that operate on that data.

Objects are instances of classes. They hold specific values for properties and can execute methods defined in the class.

Real-World Example: User Management System

class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
  }

  login() {
    console.log(`${this.username} has logged in`);
  }

  logout() {
    console.log(`${this.username} has logged out`);
  }
}

const user1 = new User('john_doe', 'john@example.com');
user1.login(); // john_doe has logged in

In this example, User is a class that defines a blueprint for user objects. Each user has a username and email, and can login and logout.

Methods and Properties

Methods are functions defined within a class. Properties are values associated with an object.

Real-World Example: E-commerce Product

class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  displayInfo() {
    console.log(`Product: ${this.name}, Price: $${this.price}`);
  }
}

const product = new Product('Laptop', 999.99);
product.displayInfo(); // Product: Laptop, Price: $999.99

Here, Product is a class representing a product in an e-commerce platform, with properties name and price, and a method displayInfo to show product details.

Inheritance in JavaScript

Inheritance allows a class to inherit properties and methods from another class using the extends keyword.

Real-World Example: Animal Hierarchy

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog('Rex');
dog.speak(); // Rex barks.

In this example, Dog inherits from Animal, so Dog instances have the name property and speak method from Animal, but Dog overrides the speak method.

Encapsulation

Encapsulation hides the internal state of an object and requires all interaction to be performed through an object’s methods.

Real-World Example: Bank Account

class BankAccount {
  #balance;

  constructor(accountNumber, balance) {
    this.accountNumber = accountNumber;
    this.#balance = balance;
  }

  deposit(amount) {
    this.#balance += amount;
    console.log(`Deposited: $${amount}. New balance: $${this.#balance}`);
  }

  withdraw(amount) {
    if (amount > this.#balance) {
      console.log('Insufficient funds');
      return;
    }
    this.#balance -= amount;
    console.log(`Withdrew: $${amount}. New balance: $${this.#balance}`);
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount('12345678', 1000);
account.deposit(500); // Deposited: $500. New balance: $1500
account.withdraw(200); // Withdrew: $200. New balance: $1300

Here, the BankAccount class encapsulates the balance using a private field #balance. Only the methods deposit, withdraw, and getBalance can access and modify the balance, protecting it from direct manipulation.

Polymorphism

Polymorphism allows methods to do different things based on the object it is acting upon.

Real-World Example: Payment Processing

class Payment {
  process() {
    console.log('Processing payment...');
  }
}

class CreditCardPayment extends Payment {
  process() {
    console.log('Processing credit card payment...');
  }
}

class PayPalPayment extends Payment {
  process() {
    console.log('Processing PayPal payment...');
  }
}

const payments = [new Payment(), new CreditCardPayment(), new PayPalPayment()];
payments.forEach(payment => payment.process());
// Output:
// Processing payment...
// Processing credit card payment...
// Processing PayPal payment...

In this scenario, different types of payments (CreditCardPayment, PayPalPayment) can be processed using the same method process, demonstrating polymorphism.

Abstraction

Abstraction hides the complex reality while exposing only the necessary parts.

Real-World Example: Coffee Machine

class CoffeeMachine {
  #waterAmount = 0;

  setWaterAmount(value) {
    if (value < 0) throw new Error("Negative water");
    this.#waterAmount = value;
  }

  getWaterAmount() {
    return this.#waterAmount;
  }

  constructor(power) {
    this._power = power;
    console.log(`Created a coffee machine with power ${power}`);
  }

  #boilWater() {
    console.log('Boiling water...');
  }

  makeCoffee() {
    this.#boilWater();
    console.log('Coffee is ready!');
  }
}

const coffeeMachine = new CoffeeMachine(1000);
coffeeMachine.setWaterAmount(200);
coffeeMachine.makeCoffee(); // Boiling water... Coffee is ready!

In this example, the CoffeeMachine class hides the complexity of boiling water and making coffee. The user interacts with the simpler interface: setting the water amount and making coffee.

For more insights on OOP concepts, refer to MDN Web Docs on Object-Oriented Programming.

Video Resources:

By understanding and applying these OOP principles, developers can write more efficient, maintainable, and scalable JavaScript code.