Education Logo Images
Blog
Education Logo Images

Batchmate is an online coding platform offering free resources to help learners at all levels build and advance their coding skills.

  • batchmate.info@gmail.com
  • Blog
Find With Us
  • Home
  • Blogs
  • Details

SOLID Principles Using Javascript

Master the SOLID principles in JavaScript to create robust, maintainable, and scalable software. Ensure clean code through single responsibility, extensibility, substitutability, focused interfaces, and effective dependency management.

  • Last updated: 01 Jun, 2025

The SOLID principles are a set of five design principles that guide object-oriented programming and design. They help developers create systems that are easy to maintain, scale, and extend. These principles are particularly useful for writing clean and maintainable code in Javascript. It was Conceptualized by Robert C. Martin, also known as Uncle Bob. Five Design principles of SOLID principles,



Single Responsibility Principle (SRP):

A class should have only one reason to change, meaning it should perform a single responsibility or task.

Example, Without SRP,

class UserManager {
  addUser(user) {
    // Add user logic
    this.validateUser(user);
    this.saveUserToDatabase(user);
    this.sendWelcomeEmail(user);
  }

  validateUser(user) {
    // Validation logic
    console.log('Validating user:', user);
  }

  saveUserToDatabase(user) {
    // Database logic
    console.log('Saving user to the database:', user);
  }

  sendWelcomeEmail(user) {
    // Email logic
    console.log('Sending welcome email to:', user.email);
  }
}

// Example usage
const userManager = new UserManager();
const user = { name: 'John Doe', email: 'john.doe@example.com' };
userManager.addUser(user);

With SRP,

class UserManager {
  constructor(userRepository, emailService) {
    this.userRepository = userRepository;
    this.emailService = emailService;
  }

  addUser(user) {
    this.userRepository.save(user);
    this.emailService.sendWelcomeEmail(user);
  }
}

class UserRepository {
  save(user) {
    // Save user to database logic
    console.log('Saving user to the database:', user);
  }
}

class EmailService {
  sendWelcomeEmail(user) {
    // Email sending logic
    console.log('Sending welcome email to:', user.email);
  }
}

// Example usage
const userRepository = new UserRepository();
const emailService = new EmailService();
const userManager = new UserManager(userRepository, emailService);

const user = { name: 'John Doe', email: 'john.doe@example.com' };
userManager.addUser(user);

Here, the UserManager, UserValidator, and EmailService each have a single responsibility.

Open/Closed Principle (OCP):

Software entities (classes, modules, functions) should be open for extension but closed for modification.

Without OCP,

class PaymentService {
  processPayment(paymentType) {
    if (paymentType === "CREDIT_CARD") {
      // Credit card processing logic
      console.log("Processing credit card payment...");
    } else if (paymentType === "PAYPAL") {
      // PayPal processing logic
      console.log("Processing PayPal payment...");
    }
  }
}

// Example usage
const paymentService = new PaymentService();
paymentService.processPayment("CREDIT_CARD");
paymentService.processPayment("PAYPAL");
With OCP,
// Define a PaymentProcessor interface (in JavaScript, we use a base class or documentation as a guideline)
class PaymentProcessor {
  processPayment() {
    throw new Error("processPayment method must be implemented");
  }
}

// CreditCardProcessor class
class CreditCardProcessor extends PaymentProcessor {
  processPayment() {
    // Credit card processing logic
    console.log("Processing credit card payment...");
  }
}

// PayPalProcessor class
class PayPalProcessor extends PaymentProcessor {
  processPayment() {
    // PayPal processing logic
    console.log("Processing PayPal payment...");
  }
}

// PaymentService class
class PaymentService {
  constructor(processors) {
    this.processors = processors;
  }

  processPayments() {
    this.processors.forEach((processor) => processor.processPayment());
  }
}

// Example usage
const processors = [new CreditCardProcessor(), new PayPalProcessor()];
const paymentService = new PaymentService(processors);

paymentService.processPayments();

New payment types can be added without modifying PaymentService.

Liskov Substitution Principle (LSP):

Subtypes must be substitutable for their base types without altering the correctness of the program.

Without LSP,

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    super.setWidth(width);
    super.setHeight(width); // Violates LSP by overriding base class behavior
  }

  setHeight(height) {
    super.setWidth(height);
    super.setHeight(height); // Violates LSP by overriding base class behavior
  }
}

// Example usage
const rectangle = new Rectangle();
rectangle.setWidth(4);
rectangle.setHeight(5);
console.log("Rectangle area:", rectangle.getArea()); // 20

const square = new Square();
square.setWidth(4);
console.log("Square area:", square.getArea()); // 16 (Expected behavior if it's a square)

const substitutedRectangle = new Square();
substitutedRectangle.setWidth(4);
substitutedRectangle.setHeight(5);
console.log("Substituted rectangle area:", substitutedRectangle.getArea()); // Incorrect behavior, 25

Problem:The Square class overrides setWidth and setHeight to maintain square-specific behavior, but when used as a Rectangle (substituted for the base type), it violates the LSP because it alters the correctness of the program.


With LSP,

// Shape interface (in JavaScript, we use a base class or documentation for such interfaces)
class Shape {
  getArea() {
    throw new Error("getArea method must be implemented");
  }
}

// Rectangle class implementing Shape
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

// Square class implementing Shape
class Square extends Shape {
  constructor(side) {
    super();
    this.side = side;
  }

  getArea() {
    return this.side * this.side;
  }
}

// Example usage
const shapes = [
  new Rectangle(4, 5), // Rectangle with width 4 and height 5
  new Square(4),       // Square with side 4
];

shapes.forEach((shape) => {
  console.log("Area:", shape.getArea());
});
  1. Separate Classes: The Rectangle and Square classes implement the Shape interface independently, each with its own logic for getArea.

  2. No Dependency on Base Class Behavior: Square no longer inherits from Rectangle, avoiding any incorrect behavior when substituted.

  3. Polymorphism: Both Rectangle and Square are substitutable for the Shape interface without altering correctness.

Here, Square and Rectangle both implement Shape without breaking substitutability.

Interface Segregation Principle (ISP):

Clients should not be forced to implement interfaces they do not use.

Without ISP,

class Vehicle {
  startEngine() {
    throw new Error("startEngine method must be implemented");
  }

  fly() {
    throw new Error("fly method must be implemented");
  }
}

class Car extends Vehicle {
  startEngine() {
    // Start engine logic
    console.log("Car engine started.");
  }

  fly() {
    throw new Error("Cars can't fly"); // Violates ISP
  }
}

// Example usage
const car = new Car();
car.startEngine(); // Works fine
car.fly(); // Throws an error: "Cars can't fly"

Problem:The Car class is forced to implement the fly method from the Vehicle interface, even though it doesn't make sense for a car to fly. This violates the Interface Segregation Principle.


With ISP,

// Define the EngineVehicle interface (base class)
class EngineVehicle {
  startEngine() {
    throw new Error("startEngine method must be implemented");
  }
}

// Define the FlyingVehicle interface (base class)
class FlyingVehicle {
  fly() {
    throw new Error("fly method must be implemented");
  }
}

// Car class implements only EngineVehicle
class Car extends EngineVehicle {
  startEngine() {
    // Start engine logic
    console.log("Car engine started.");
  }
}

// Airplane class implements both EngineVehicle and FlyingVehicle
class Airplane extends EngineVehicle {
  startEngine() {
    // Start engine logic
    console.log("Airplane engine started.");
  }

  fly() {
    // Fly logic
    console.log("Airplane is flying.");
  }
}

// Example usage
const car = new Car();
car.startEngine(); // "Car engine started."

const airplane = new Airplane();
airplane.startEngine(); // "Airplane engine started."
airplane.fly(); // "Airplane is flying."
  1. Separate Interfaces:
    • EngineVehicle defines the startEngine method.
    • FlyingVehicle defines the fly method.
  2. Class Implementation:
    • Car implements only EngineVehicle since it doesn't need flying functionality.
    • Airplane implements both EngineVehicle and FlyingVehicle since it needs both functionalities.
  3. Adheres to ISP: Classes implement only the methods they need, avoiding unnecessary implementation of unused methods.

Dependency Inversion Principle (DIP):

High-level modules should not depend on low-level modules; both should depend on abstractions.

Without DIP,

class UserRepository {
  save(user) {
    // Save logic
    console.log(`Saving user: ${user.name}`);
  }
}

class UserService {
  constructor() {
    this.userRepository = new UserRepository(); // High-level module directly depends on a low-level module
  }

  saveUser(user) {
    this.userRepository.save(user);
  }
}

// Example usage
const userService = new UserService();
userService.saveUser({ name: "John Doe" });

Problem:The UserService class (high-level module) depends directly on the UserRepository class (low-level module), creating tight coupling between the two.


With DIP,

// Define the UserRepository interface
class UserRepository {
  save(user) {
    throw new Error("save method must be implemented");
  }
}

// Implement the JpaUserRepository class (low-level module)
class JpaUserRepository extends UserRepository {
  save(user) {
    // Save logic
    console.log(`Saving user: ${user.name}`);
  }
}

// UserService depends on the abstraction (UserRepository), not the concrete implementation
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository; // Dependency injected
  }

  saveUser(user) {
    this.userRepository.save(user);
  }
}

// Example usage
const userRepository = new JpaUserRepository(); // Low-level module
const userService = new UserService(userRepository); // High-level module depends on abstraction
userService.saveUser({ name: "John Doe" }); // Output: Saving user: John Doe

By depending on UserRepository, the UserService can work with any repository implementation.

  1. Abstraction:The UserRepository class acts as an interface that declares the save method.

  2. Low-Level Module: The JpaUserRepository class extends UserRepository and provides a concrete implementation of the save method.

  3. High-Level Module: The UserService class depends on the UserRepository abstraction, not a concrete implementation.

  4. Dependency Injection: The UserRepository dependency is passed into UserService via its constructor, decoupling the two and allowing flexibility to swap out implementations.


By following these SOLID principles, you ensure your Java code is modular, reusable, and easier to maintain.

SOLID Principles
Related Post

Similar Post

Card image
SOLID Principles Using PHP
  • 04 Jun, 2025
  • 3 min read

Master SOLID principles to build resilient, scalable software with maintainable, extensible, and decoupled code that is easier to test and evolve.

Learn More
Card image
SOLID Principles Using Javascript
  • 04 Jun, 2025
  • 3 min read

Master the SOLID principles in JavaScript to create robust, maintainable, and scalable software. Ensure clean code through single responsibility, extensibility, substitutability, focused interfaces, and effective dependency management.

Learn More

Edu-cause

We’re here to support you in achieving your dream career!

Useful Links
  • Home
  • Terms and Conditions
  • Privacy Policy
Our Company
  • Faq
  • Blog
  • Contact Us
Get Contact
  • E-mail: batchmate.info@gmail.com

Copyright © 2024 Batchmate. All Rights Reserved