SOLID Principles (Part 1 - SRP, OCP, LSP)
Deep dive into the first three SOLID principles: Single Responsibility, Open-Closed, and Liskov Substitution, with practical examples of violations and corrections.
Learning Goals
- Identify SRP violations in monolithic classes.
- Apply the Open-Closed Principle using interfaces.
- Validate Liskov Substitution to ensure robust inheritance hierarchies.
SOLID Principles (Part 1): SRP, OCP, and LSP
The SOLID principles are a set of five design guidelines intended to make software designs more understandable, flexible, and maintainable. In this section, we cover the first three.
1. Single Responsibility Principle (SRP)
"A class should have one, and only one, reason to change."
SRP is often misunderstood as "a class should do only one thing." More accurately, it means a class should be responsible for only one actor or business logic area.
Violation Example
Imagine an Employee class that calculates salary, generates a report, and saves the employee data to a database.
- If the accounting logic changes, the class changes.
- If the report format changes, the class changes.
- If the database schema changes, the class changes.
Better Design: Split these into Employee, SalaryCalculator, ReportGenerator, and EmployeeRepository.
2. Open-Closed Principle (OCP)
"Software entities should be open for extension, but closed for modification."
You should be able to add new functionality without changing existing code. This is usually achieved through abstraction (interfaces or abstract classes).
Visualizing OCP with Mermaid
With the IDiscount interface, we can add a SeniorCitizenDiscount without touching BetterDiscountCalculator.
3. Liskov Substitution Principle (LSP)
"Subtypes must be substitutable for their base types."
If class B is a subclass of class A, then we should be able to replace A with B without breaking the program. LSP is violated if a subclass changes the expected behavior of the base class in a way that surprises the caller (e.g., throwing a NotImplementedException).
The Classic Example: Square vs. Rectangle
If a Rectangle has setWidth and setHeight, and a Square inherits from it but forces both dimensions to be the same, it might break a function that expects to be able to set them independently.
Solution: Use a common interface IShape or IQuadrilateral that doesn't make assumptions about independent dimensions if they don't apply to all subclasses.
Knowledge Check
Which principle is most likely being violated if you find yourself frequently using 'switch' or 'if-else' statements to check object types?