Skip to content

About

SOLID design principles implemented in TypeScript and Node.js

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

solid_principal_conecpt_node

focusing only on SOLID principles with Node.js

SOLID Principles in Node.js

1. Single Responsibility Principle (SRP)

Definition: A class (or module) should have only one reason to change, meaning it should have only one job.

Example in Node.js

// BAD: Handles both order processing and email notifications
class OrderService {
    processOrder(order) {
        console.log("Processing order: ", order);
        this.sendEmail(order);
    }
    sendEmail(order) {
        console.log(`Email sent for order: ${order.id}`);
    }
}
// GOOD: Separating concerns
class OrderService {
    constructor(emailService) {
        this.emailService = emailService;
    }
    processOrder(order) {
        console.log("Processing order: ", order);
        this.emailService.sendEmail(order);
    }
}
class EmailService {
    sendEmail(order) {
        console.log(`Email sent for order: ${order.id}`);
    }
}
const emailService = new EmailService();
const orderService = new OrderService(emailService);
orderService.processOrder({ id: 123 });

Interview Questions:

  1. What is SRP and why is it important in software design?
  2. How does SRP help in writing testable code?
  3. Can you give an example where violating SRP caused problems?

2. Open/Closed Principle (OCP)

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

Example in Node.js

// BAD: Adding new payment methods requires modifying existing code
class PaymentService {
    pay(order, type) {
        if (type === "CreditCard") {
            console.log("Processing Credit Card payment");
        } else if (type === "PayPal") {
            console.log("Processing PayPal payment");
        }
    }
}
// GOOD: Using abstraction to extend functionality
class PaymentProcessor {
    processPayment(order) {
        throw new Error("Method not implemented");
    }
}
class CreditCardPayment extends PaymentProcessor {
    processPayment(order) {
        console.log("Processing Credit Card payment");
    }
}
class PayPalPayment extends PaymentProcessor {
    processPayment(order) {
        console.log("Processing PayPal payment");
    }
}
const paymentMethods = [new CreditCardPayment(), new PayPalPayment()];
paymentMethods.forEach(method => method.processPayment({ id: 123 }));

Interview Questions:

  1. How does OCP improve maintainability?
  2. What are some real-world examples of OCP?
  3. How do interfaces help in following OCP?

3. Liskov Substitution Principle (LSP)

Definition: Objects of a superclass should be replaceable with objects of a subclass without breaking the application.

Example in Node.js

// BAD: Violates LSP because JSONStorage changes the return type
class Database {
    save(data) {
        console.log("Saving to DB", data);
    }
}
class JSONStorage extends Database {
    save(data) {
        return JSON.stringify(data); // Violates LSP
    }
}
// GOOD: Maintains expected behavior
class Database {
    save(data) {
        console.log("Saving to DB", data);
    }
}
class JSONStorage extends Database {
    save(data) {
        super.save(JSON.stringify(data));
    }
}

Interview Questions:

  1. What is LSP and why is it important?
  2. How do you ensure LSP in a system with multiple subclasses?
  3. What issues might arise from violating LSP?

4. Interface Segregation Principle (ISP)

Definition: Clients should not be forced to depend on interfaces they do not use.

Example in Node.js

// BAD: Single interface forces unused methods
class Worker {
    work() {
        console.log("Working...");
    }
    eat() {
        console.log("Eating...");
    }
}
class Robot extends Worker {
    eat() {
        throw new Error("Robots don’t eat"); // Violates ISP
    }
}
// GOOD: Separate interfaces
class Workable {
    work() {}
}
class Eatable {
    eat() {}
}
class Human extends Workable {
    eat() {
        console.log("Eating...");
    }
    work() {
        console.log("Working...");
    }
}
class Robot extends Workable {
    work() {
        console.log("Working without eating");
    }
}

Interview Questions:

  1. How does ISP improve modularity?
  2. Can you give an example where violating ISP caused issues?
  3. How do you refactor a large interface following ISP?

5. Dependency Inversion Principle (DIP)

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

Example in Node.js

// BAD: High-level module directly depends on low-level module
class MySQLDatabase {
    connect() {
        console.log("Connected to MySQL");
    }
}
class UserService {
    constructor() {
        this.database = new MySQLDatabase(); // Violates DIP
    }
    getUser() {
        this.database.connect();
    }
}
// GOOD: Using dependency injection and abstraction
class Database {
    connect() {}
}
class MySQLDatabase extends Database {
    connect() {
        console.log("Connected to MySQL");
    }
}
class UserService {
    constructor(database) {
        this.database = database;
    }
    getUser() {
        this.database.connect();
    }
}
const database = new MySQLDatabase();
const userService = new UserService(database);
userService.getUser();

Interview Questions:

  1. How does DIP help in decoupling modules?
  2. What are the benefits of using dependency injection?
  3. How would you refactor an application that violates DIP?

Conclusion

Understanding and applying SOLID principles in Node.js applications ensures better maintainability, testability, and scalability. These principles help in writing cleaner, modular, and robust code for real-world applications.

Need help implementing SOLID in your project? 🚀

About

SOLID design principles implemented in TypeScript and Node.js

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages