Understanding the four Pillars of Object-Oriented Programming using JavaScript 💊

adhirajk

Adhiraj Kinlekar

Posted on November 11, 2023

Understanding the four Pillars of Object-Oriented Programming using JavaScript 💊

Object Oriented Programming

Object-Oriented Programming is a programming paradigm that enables you to model and structure your code using objects and classes. JavaScript is not a full-fledged OOP language, but you can still leverage OOP's core principles to write cleaner and more maintainable code. There are four main pillars of Object-Oriented Programming:

Abstraction

Abstraction means hiding the complex implementation details and exposing only what is necessary. Even though JavaScript lacks interfaces or abstract classes, we can still achieve abstraction through other means.

One effective approach for implementing abstraction is to expose only the necessary methods and then, through this exposed method, invoke the private methods of the class. This strategy effectively conceals the underlying complexities, which is a fundamental aspect of abstraction.

class Auth {

    constructor() {}

    #validate() {
    // Private method
    }

    #sendOnboardingEmail() {
    // Private method
    }

    // Public method that calls private methods
    signUp() {
      this.#validate();
      // Execute signUp functionality
      this.#sendOnboardingEmail();
    }
}

const auth = new Auth();

// Calls the public method, which, in turn, calls private methods
auth.signUp(); 
Enter fullscreen mode Exit fullscreen mode

Encapsulation

When you search the internet for information about Abstraction and Encapsulation, you will likely come across numerous articles that sometimes present conflicting ideas. In my understanding, although abstraction and encapsulation are different concepts, they often complement each other. In the code block above, private accessors are utilized, enabling controlled access to the class, aligning with the principles of Encapsulation. Encapsulation promotes the idea of bundling data and the functions that operate on that data into a single, self-contained package. This encapsulated entity can control how the data is accessed, modified, or interacted with.

Even though Encapsulation is an OOP concept, it can be implemented without the use of classes and objects by utilizing Closures. A closure is a mechanism that enables an inner function to access the variables and parameters of its outer function, even after the outer function has completed its execution. Closures achieve encapsulation by packaging the actual code (the body of the function) along with the variables and parameters that the function can access during its execution. The exclusive method for accessing the encapsulated data is through the function.

function createCounter() {

  let count = 0; // This variable is encapsulated within the closure.

  function increment() {
    count++; // The inner function can access the 'count' variable.
    console.log(count)
  }

  return increment; // Return the inner function (closure).
}

const counter = createCounter(); 
Enter fullscreen mode Exit fullscreen mode

Inheritance

When a class acquires the members and behaviors of its parent class, it is known as Inheritance. Inheritance provides code reusability and encourages modular design by breaking down a complex system into smaller, manageable components. When you need to make changes or updates to shared functionality, you can do so in the base class. These changes automatically apply to all derived classes, reducing maintenance efforts and ensuring consistency throughout your codebase.

class Person {

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

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

// Create a derived class Student that inherits from Person
class Student extends Person {

  constructor(name, age, studentId) {
    super(name, age); // Call the parent class constructor using super
    this.studentId = studentId;
  }

  study(subject) {
    console.log(`${this.name} with student ID ${this.studentId} is studying ${subject}.`);
  }
}

// Create instances of Person and Student
const person1 = new Person('Alice', 30);

const student1 = new Student('Bob', 25, '12345');

// Use the inherited methods
person1.sayHello();

student1.sayHello();

student1.study('Math');
Enter fullscreen mode Exit fullscreen mode

Polymorphism

The term 'polymorphism' means having many forms. The concept of polymorphism enables us to perform different operations in various scenarios. In object-oriented programming languages such as C#, polymorphism is achieved through the use of interfaces and abstract classes, as well as through the use of virtual methods and overriding in inheritance. While JavaScript does not provide comprehensive support for polymorphism, we can still achieve it.

Polymorphism can be attained by using inheritance and by overriding the base class. You do not need to explicitly indicate that a method is being overridden since JavaScript uses a prototype-based inheritance model, and method overriding is accomplished simply by defining a method with the same name in a subclass. The new method in the subclass effectively replaces the method with the same name in the base class, allowing you to perform different actions in different scenarios, which aligns with the concept of polymorphism

class Transport {

  constructor() {}

  drive() {
    console.log("Driving...");
  }

  fly() {
    console.log("Flying...");
  }
}

// Create a derived class Car that inherits from Transport
class Car extends Transport {

  constructor() {
    super();
  }

// Overrides it's base implementation
  fly() {
    console.log("I am a car, I cannot fly");
  }
}

const audi = new Car();

audi.fly();
Enter fullscreen mode Exit fullscreen mode

Song of the day : Agalloch - Hawthorne Passage

💖 💪 🙅 🚩
adhirajk
Adhiraj Kinlekar

Posted on November 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related