Mastering SOLID Principles in Software Development with Real-World Analogies - Part 2

Mastering SOLID Principles in Software Development with Real-World Analogies - Part 2

In Part 1, we got to know about what are SOLID principles in software development, and three of the principles that make up the acronym SOLID: this includes Single Responsibility Principle (SRP), Open/Closed Principle (OCP), and Liskov Substitution Principle (LSP). In Part 2, we will look at the other two principles: Interface Segregation Principle (ISP), Dependency Inversion Principle (DIP). So let’s check it out! 

4. Interface Segregation Principle (ISP)

 

Interface Segregation Principle (ISP)

 

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

The Interface Segregation Principle (ISP) encourages designing small, specific interfaces rather than large, general-purpose ones. This makes it easier to implement only the required methods and prevents classes from being burdened by unnecessary functionality.

Real-World Analogy:

Here’s how implementing ISP in Object-Oriented Design works! Think about different types of remote controls. A TV remote has buttons for changing channels and volume, while an air conditioner remote has buttons for temperature control and fan speed. If you had a universal remote with all possible buttons, it would be confusing and harder to use. Instead, each remote has only the controls it needs.

Explanation:

ISP advocates breaking down large interfaces into smaller ones that are more specific to the needs of their clients. This leads to cleaner, more maintainable code, as classes aren’t forced to implement methods they don’t need.

 

Without Interface Segregation Principle

abstract class PaymentProcessor {

  void processCreditCardPayment(double amount);

  void processPaypalPayment(double amount);

  void processBitcoinPayment(double amount);

}

 

class CreditCardPayment implements PaymentProcessor {

  @override

  void processCreditCardPayment(double amount) {

    // Process credit card payment

  }

 

  @override

  void processPaypalPayment(double amount) {

    // Not implemented

  }

 

  @override

  void processBitcoinPayment(double amount) {

    // Not implemented

  }

}


Explanation:

In this version, the PaymentProcessor interface defines methods for processing multiple types of payments: credit card, PayPal, and Bitcoin. However, the CreditCardPayment class only implements credit card payments. The other methods (processPaypalPayment and processBitcoinPayment) are not needed and remain unimplemented, violating the Interface Segregation Principle (ISP).

The Interface Segregation Principle suggests that no class should be forced to implement methods it does not need. By having a general PaymentProcessor interface with multiple payment methods, we are imposing unnecessary requirements on classes like CreditCardPayment, leading to unused or empty methods, making the design inefficient.

 

With Interface Segregation Principle

abstract class CreditCardPaymentProcessor {

  void processCreditCardPayment(double amount);

}

 

abstract class PaypalPaymentProcessor {

  void processPaypalPayment(double amount);

}

 

abstract class BitcoinPaymentProcessor {

  void processBitcoinPayment(double amount);

}

 

class CreditCardPayment implements CreditCardPaymentProcessor {

  @override

  void processCreditCardPayment(double amount) {

    // Process credit card payment

  }

}

 

Explanation:

In the refactored version, we follow the Interface Segregation Principle by splitting the general PaymentProcessor interface into smaller, more specific interfaces:

  • CreditCardPaymentProcessor for processing credit card payments.
  • PaypalPaymentProcessor for processing PayPal payments.
  • BitcoinPaymentProcessor for processing Bitcoin payments.

Each payment type now has its own dedicated interface, which means that a class like CreditCardPayment only implements the CreditCardPaymentProcessor interface, containing only the relevant method (processCreditCardPayment). There are no longer unneeded or unimplemented methods, which makes the class cleaner and more focused on its specific task.

 

5. Dependency Inversion Principle (DIP)

Dependency Inversion Principle (DIP)

 

High-level modules should not depend on low-level modules. Both should depend on abstractions.

The Dependency Inversion Principle (DIP) ensures that high-level components depend on abstractions (interfaces or abstract classes) rather than concrete implementations. This reduces coupling and makes the system more flexible and easier to maintain.

 

Real-World Analogy:

Consider a home electricity system. The appliances (high-level modules) shouldn’t depend on the specific wiring (low-level modules). Instead, both depend on a standard power socket interface. This allows you to plug in any appliance without needing to worry about how the wiring works.


Explanation:

DIP promotes loose coupling in software engineering between software components by ensuring that higher-level logic depends on abstract interfaces, not specific implementations. This makes it easy to swap out components or extend functionality without modifying the high-level logic.

 

Without Dependency Inversion Principle

class SqlDatabase {

  void saveData(String data) {

    // Save data to SQL database

  }

}

 

class DataRepository {

  final SqlDatabase database = SqlDatabase();

 

  void save(String data) {

    database.saveData(data);

  }

}

 

Explanation:

In this version, the DataRepository class depends directly on the concrete SqlDatabase class, which violates the Dependency Inversion Principle (DIP). According to DIP, high-level modules (like DataRepository) should not depend on low-level modules (like SqlDatabase). Both should depend on abstractions. This tight coupling between DataRepository and SqlDatabase makes it difficult to switch to a different database (like NoSQL) or mock the database for testing without modifying the DataRepository class.

 

With Dependency Inversion Principle

abstract class Database {

  void saveData(String data);

}

 

class SqlDatabase implements Database {

  @override

  void saveData(String data) {

    // Save data to SQL database

  }

}

 

class NoSqlDatabase implements Database {

  @override

  void saveData(String data) {

    // Save data to NoSQL database

  }

}

 

class DataRepository {

  final Database database;

 

  DataRepository(this.database);

 

  void save(String data) {

    database.saveData(data);

  }

}

 

Explanation:

In this refactored version, we apply the Dependency Inversion Principle by introducing an abstraction, Database, that defines the contract for saving data. Now, the DataRepository class depends on this abstraction instead of the concrete SqlDatabase class.

  • SqlDatabase and NoSqlDatabase both implement the Database interface, which allows for different types of databases without modifying the DataRepository class.
  • The DataRepository class is now flexible and can work with any class that implements the Database interface.

 

Conclusion

Conclusion

 

 

 

 

The SOLID principles form the foundation of writing clean, maintainable, and scalable software. Whether you're working with Flutter, web technologies, or any other platform, adhering to these principles will help you create systems that are easier to extend, modify, and maintain.

  • Single Responsibility Principle ensures that each class or function has a clear and focused responsibility.
  • Open/Closed Principle encourages building systems that can grow without breaking existing functionality.
  • Liskov Substitution Principle guarantees that derived classes behave correctly when substituted for their base classes.
  • Interface Segregation Principle advocates for small, specific interfaces that provide exactly what is needed.
  • Dependency Inversion Principle promotes loose coupling by depending on abstractions rather than concrete implementations.

By applying these principles, you can create more robust and flexible software, regardless of the language or framework you're using. Get in touch with our software developers to know more!

Zia Khan
Zia Khan
Software Engineer
Developing Leadership Skills and Taking Accountability How-To Guide

Developing Leadership Skills and Taking Accountability: How-To Guide

SHAHED ISLAM
TEAM Together, Everyone, Achieves More

T (Together) E (Everyone) A (Achieves) M (More)

SIMON CORREIA
Secret Skills Needed By a Perfect Team

Secret skills of a Perfect Team

LAVINA FARIA