the programming paradigm comparison between FP and OOP

the programming paradigm comparison between FP and OOP

A programming paradigm is a fundamental approach or style of programming that describes how solutions to problems can be formulated using programming languages.Each programming paradigm is baesd on a set of principles,concepts, and practices that guide the process of software design and development.Here are two programming paradigms that will be differentiated.

The programming paradigm is a way of thinking about and structuring computer programs. For developers, it means how you write your code or organize your code.

Functional Programming

FP is functional programming, which should use pure functions and avoid side effects. the pure function means that the outputs in this function only depend on the inputs. the input paragram can be anything like numbers or functions as well as the outputs. the pure function is easier to test and reason about.

 let num=123
 // it is a pure funciton which only depends on the input val and don't change and depend on any outside value
 function toString(val){
   return val.toString()
 }
 const str=toString(num)

 // make a impure function. it changed the outside value num,which is a side effect
 function toString(val){
  num=val
  return val.toString()
 }

A side effect is any modification of the state of the system or the outside world which is caused by a function. Side effects usually include things like modifying a variable outside a function, writing to a file, making a network request, or modifying the DOM. Functions with no side effects are often referred to as pure functions. Although having pure functions is the best option, it is indeed necessary to require some functions with side effects such as writing data to a database or making an API call. In these cases, it is important to manage the side effects carefully and to isolate them as much as possible from the rest of the code for sake of minimizing their impact on the rest of the code.

// these are side effects in JS
//Modifying a variable outside the function
let count = 0;

function increment() {
  count++; // modifies the count variable outside the function
}
//Modifying the DOM
function updateTitle(title) {
  document.title = title; // modifies the title of the document
}
// making a network request
function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json());
}
//Modifying a global object
function setLanguage(lang) {
  window.navigator.language = lang; // modifies the language setting of the browser
}

Another core principle is immutable data, which means it is never mutated once the data is created. it is easier to keep track of and test with immutable data. This also helps to prevent unintended side effects and makes programs more predictable. Instead of modifying data in place, functional programming relies on creating new values and returning them as results. In addition to immutability and pure function, higher-order functions and recursion are key features as well. Higher-order functions are which take other functions as arguments or return functions as results. They enable powerful abstractions and help to reduce code duplication. recursion is used to solve problems by breaking them into smaller, similar problems.
all in all, functional programming emphasizes immutability, pure functions, and higher-order functions, which can lead to code that is more predictable, more testable, easier to reason about, and less prone to bugs.

Object-oriented Programming

OOP mainly focuses on the concept of objects and classes, which provides a way to organize codes into reusable and self-contained pieces that model real-world entities. It is primarily comprised of four features:

  • Encapsulation: Encapsulation means that data and methods can be grouped together within a single unit, called a class. The class provides a way to restrict access to the data and methods from the outside, which helps to prevent unwanted modification and ensures that the behavior of the class remains consistent. This is the practice of hiding the implementation details of an object and exposing only the necessary information. Encapsulation allows you to protect the internal state of an object from external interference, which helps ensure the consistency and reliability of the system.

    class BankAccount {
      private accountNumber: string;
      private balance: number;
    
      constructor(accountNumber: string, initialBalance: number) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
      }
    
      public deposit(amount: number) {
        this.balance += amount;
      }
    
      public withdraw(amount: number) {
        if (this.balance >= amount) {
          this.balance -= amount;
        } else {
          console.log("Insufficient balance.");
        }
      }
    
      public getAccountNumber(): string {
        return this.accountNumber;
      }
    
      public getBalance(): number {
        return this.balance;
      }
    }
    
    const account = new BankAccount("123456789", 1000);
    account.deposit(500);
    account.withdraw(200);
    console.log(`Account Number: ${account.getAccountNumber()}, Balance: ${account.getBalance()}`);
    

    In this example, the BankAccount class is an example of encapsulation. The private properties accountNumber and balance are hidden from outside access but can be accessed through public methods getAccountNumber() and getBalance(). This allows us to protect the internal state of the object, while still allowing the object to be manipulated through a defined interface. The deposit() and withdraw() methods are also public, but they can only be called by the object itself or its methods, preventing outside interference.

  • Inheritance: This is the ability of one object to inherit properties and methods from another object. Inheritance allows you to create new objects that have the same behavior and properties as existing objects, but with added functionality.

    class Animal {
    protected name: string;
    
    constructor(name: string) {
      this.name = name;
    }
    
    public move(distance: number = 0) {
      console.log(`${this.name} moved ${distance} meters.`);
    }
    }
    
    class Dog extends Animal {
      private breed: string;
    
      constructor(name: string, breed: string) {
        super(name);
        this.breed = breed;
      }
    
      public bark() {
        console.log("Woof! Woof!");
      }
    
      public move(distance: number = 5) {
        console.log(`${this.name} trotted ${distance} meters.`);
      }
    }
    
    const dog = new Dog("Buddy", "Labrador");
    dog.bark();
    dog.move();
    console.log(`Name: ${dog.name}, Breed: ${dog['breed']}`);
    

    In this example, the Animal class is the parent class, and the Dog class is the child class that inherits from it. The Dog class extends the Animal class using the extends keyword, which allows it to inherit all the properties and methods of the Animal class. Furthermore, the Dog class also adds a new private property breed and a new public method bark.

  • Polymorphism: This is the ability of objects to take on different forms or types. Polymorphism allows you to use the same interface or method to perform different actions based on the type of object that is being manipulated.
    class Cat extends Animal{
      private run: string;
       constructor(name: string, run: string) {
        super(name);
        this.run = run;
      }
      public move(distance: number = 10) {
        console.log(`${this.name} trotted ${distance} meters.`);
      }
    }
    const animals: Animal[] = [
    new Cat("cat1", 10),
    new Dog("Dog 1",  6),
    new Cat("cat1 2", 15),
    new Dog("Dog 2",  12),
    ];
    animals.forEach((animal)=>{
      console.log(animal.move())
    })
    

    For example, like the above codes, the Dog class and Cat class have overridden the move() method. When we call this method, the actual implementation depends on the type of animal. Simple speaking, polymorphism is the different implementation for the same method based on the inheritance to the same class.

  • Abstraction: This is the process of identifying common patterns and behaviors across different objects and using them to create a generalized, abstract concept. Abstraction allows you to create more modular and reusable code, which can reduce development time and improve code quality.

with these features, we can create more complex, flexible, and maintainable software systems that can scale to meet the demands of modern applications. In addition to these features, the SOLID design principles should be followed for sake of writing maintainable and scalable object-oriented software.

the principles are as follows:

  1. Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have only one responsibility or job to do. This promotes better maintainability and testability of the code.
  1. Open/Closed Principle (OCP): A class should be open for extension but closed for modification. This means that the behavior of a class should be easily extendable without modifying the existing code.
  1. Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types. This means that objects of a subclass should be able to replace objects of their superclass without causing any problems.
  1. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use. This means that interfaces should be designed to be as cohesive as possible, with each interface representing a specific behavior or set of behaviors.
  1. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions. This means that the dependencies between classes should be managed through abstractions, rather than through direct references.

Which one to choose in JS

JavaScript is a multi-paradigm language that supports both OOP and FP. The choice largely depends on the project requirements and personal preferences of the developers.
If the project involves a lot of data processing and transformations and requires a high degree of predictability and reliability, then FP may be a good choice. JavaScript has a number of built-in functional programming features, such as higher-order functions, map/reduce/filter methods, and arrow functions, which make it easy to write functional code.

On the other hand, if the project involves complex interactions between objects and requires a high degree of modularity and maintainability, then OOP may be a good choice. JavaScript supports OOP features such as classes, inheritance, and polymorphism, which can help make code more modular and easier to maintain.

In many cases, a combination of both paradigms may be appropriate for a given project, and it’s up to the developers to determine the best approach based on the specific requirements and constraints of the project.