Skip to main content

Solid Principles

Introduction to SOLID principles

SOLID design Principles

Note: This marks the beginning of a 6-part article series on SOLID principles. Each installment will explore one SOLID design principle, accompanied by examples.

Object of SOLID principles is to improve.

  • Readability.
  • maintainability
  • Reusability
  • Testability
  • Scalability

of your code.

A lot has been discussed about SOLID principles. Yet, whether reading about them or explaining them to others, confusion often arises. Many questions come to mind when exploring SOLID design principles.

We get questions like.

  • Are these principles applicable to every problem we solve as software engineers?
  • Is there a way to know if I’m violating these principles? Can we use a tool, like a linter, to alert us when we break these rules?
  • Are these principles only meant for object-oriented programming languages?
  • How can I differentiate between the different principles in SOLID? They seem quite similar to me.
  • To fix a code which violates a SOLID principles, Its solution is almost same that we used to solve other SOLID principle. Why is that, Why the code structure provided in examples of each principle looks the same.
  • Can we apply these principles to new code in a project, or are they only useful for existing projects that don’t currently follow these principles?
  • How much emphasis should I put on each principle? Is one more important than others?
  • Are there trade-offs to using SOLID? Do they take longer to implement?
  • How do I prioritize applying SOLID when deadlines are tight?
  • What resources can help me learn SOLID better? Books, tutorials, online courses?
  • How can I use SOLID to write cleaner and more maintainable code?

May be after reading this article series you have less confusion about SOLID principles.

The Fundamentals of Code Organization:

  1. Code Readability Matters: As software engineers, our daily tasks involve writing and modifying code. Since code is read multiple times, prioritizing readability is essential for effective collaboration.
  2. Organizing Code into Modules: Code is structured into modules, which could be classes, individual source files, or exported objects. Regardless of the language, decisions on how to organize and group code into distinct units are crucial.
  3. Managing Internal and External Code: Code can be either internal (used within your team) or external (utilized by other teams or external customers via an API). Decisions on visibility become necessary, determining what code is “visible” for broader use and what remains “hidden.”

SOLID principles helps in achieving code organization which makes it easier to modify, test, reuse etc.

Before jumping into SOLID principles Let’s understand coupling and cohesion.

Coupling is a measure of how dependent two or more software modules are on each other. It describes how closely they are interconnected and how changes in one module can affect others.

/***
High coupling
*/
class CustomerManager {
    private Database database;
    private EmailSender emailSender;

    public CustomerManager(Database database, EmailSender emailSender) {
        this.database = database;
        this.emailSender = emailSender;
    }

    public void createCustomer(Customer customer) {
        // ... validation logic

        database.insertCustomer(customer);
        emailSender.sendWelcomeEmail(customer);
    }

    public void updateCustomer(Customer customer) {
        // ... validation logic

        database.updateCustomer(customer);
        emailSender.sendUpdateNotificationEmail(customer);
    }

    // ... other customer-related operations
}

/***
Issues:
  - High Coupling: CustomerManager directly depends on Database and EmailSender, making it difficult to change or test these components independently.
  - Multiple Responsibilities: It handles customer management, database interactions, and email sending, violating SRP.
*//***
Refactoring to loose coupling.
*/

interface CustomerRepository {
    void saveCustomer(Customer customer);
    void updateCustomer(Customer customer);
    // ... other repository methods
}

interface EmailService {
    void sendWelcomeEmail(Customer customer);
    void sendUpdateNotificationEmail(Customer customer);
    // ... other email methods
}

class CustomerService {
    private final CustomerRepository customerRepository;
    private final EmailService emailService;

    public CustomerService(CustomerRepository customerRepository, EmailService emailService) {
        this.customerRepository = customerRepository;
        this.emailService = emailService;
    }

    public void createCustomer(Customer customer) {
        // ... validation logic

        customerRepository.saveCustomer(customer);
        emailService.sendWelcomeEmail(customer);
    }

    public void updateCustomer(Customer customer) {
        // ... validation logic

        customerRepository.updateCustomer(customer);
        emailService.sendUpdateNotificationEmail(customer);
    }

    // ... other customer-related operations
}

// Concrete implementations for testing or production
class DatabaseCustomerRepository implements CustomerRepository {
    // ... implementation using database
}

class SmtpEmailService implements EmailService {
    // ... implementation using SMTP
}


/***
Improvements:
  - Loose Coupling: Dependencies are managed through interfaces, allowing for flexible testing and replacement of implementations.
  - Single Responsibility: CustomerService focuses on customer management, delegating database and email tasks to separate services.
  - Testability: Components can be tested in isolation using mock implementations for dependencies.
*/

Cohesion measures how closely the elements within a single module are related to each other. It reflects how well they work together to achieve a single purpose.

/***
Low cohesion
*/

class UtilityClass {
    public void calculateTax(double amount) { ... }
    public void sendEmail(String recipient, String message) { ... }
    public void formatText(String text) { ... }
}

/***
Unrealted functionalities are grouped into same class.
*//***
High cohesion
*/

class OrderService {
    public void createOrder(Order order) { ... }
    public void updateOrder(Order order) { ... }
    public void cancelOrder(Order order) { ... }
}

class EmailService {
    public void sendOrderConfirmationEmail(Order order) { ... }
    public void sendShippingNotificationEmail(Order order) { ... }
}

/***
  Only related function are kept into same class.
*/
Strive for low coupling and high cohesion: This leads to more maintainable, reusable, and testable code.Minimize dependencies between modules/Class/functions: Use interfaces, dependency injection, and messaging for loose coupling.Group related elements within modules/Class/Function: Ensure elements within a module/class/function work cohesively towards a common goal.

What is SOLID principles?

The SOLID principles are a set of five fundamental design principles in object-oriented programming that aim to create robust, scalable, and maintainable software.

  1. Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have a single, well-defined responsibility.
  2. Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification, allowing new functionality to be added without altering existing code.
  3. Liskov Substitution Principle (LSP): Subtypes should be substitutable for their base types without altering the correctness of the program, ensuring smooth interchangeability between base and derived classes.
  4. Interface Segregation Principle (ISP): A class should not be forced to implement interfaces it does not use. Instead of having large, general-purpose interfaces, create smaller, more specific ones.
  5. 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 promotes flexibility and reduces code coupling.

Thanks for reading, We will cover each principle one by one in subsequent articles.