The SOLID principles are a set of five object-oriented design principles that aim to make software designs more maintainable, scalable, and flexible. Each principle focuses on a specific aspect of good design and encourages developers to write code that is easy to understand and change. Let's understand each SOLID principle with a simple example:
Single Responsibility Principle (SRP):
A class should have only one reason to change, i.e., it should have a single responsibility.
Example:
class Employee {void calculateSalary() {
// Code to calculate salary
}
void saveEmployeeData() {
// Code to save employee data to the database
}
void generateReport() {
// Code to generate an employee report
}
}
In this example, the
Employee
class is handling three distinct responsibilities: calculating salary, saving data, and generating reports. Instead, we should separate these responsibilities into different classes.- // Good Design - Separate classes for each responsibility class Employee { void calculateSalary() { // Code to calculate salary } } class EmployeeDataRepository { void saveEmployeeData() { // Code to save employee data to the database } } class EmployeeReportGenerator { void generateReport() { // Code to generate an employee report } }
Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification.
Example: // Bad Design - Modification needed to add a new shape class AreaCalculator { double calculateArea(Shape shape) { if (shape instanceof Circle) { Circle circle = (Circle) shape; // Code to calculate area of circle } else if (shape instanceof Rectangle) { Rectangle rectangle = (Rectangle) shape; // Code to calculate area of rectangle } else { // Code to calculate area of other shapes } } }
In this example, to add a new shape (e.g., Triangle), we need to modify the
AreaCalculator
class. This violates the OCP. Instead, we can use polymorphism and abstract classes/interfaces to extend the functionality. // Good Design - Open for extension, closed for modification interface Shape { double calculateArea(); } class Circle implements Shape { // Implement calculateArea for circle } class Rectangle implements Shape { // Implement calculateArea for rectangle } // Adding a new shape (e.g., Triangle) does not require modifying existing code class Triangle implements Shape { // Implement calculateArea for triangle }
Liskov Substitution Principle (LSP):
Subtypes (derived classes) should be substitutable for their base types (parent classes).
Example: // Bad Design - Subtypes not substitutable class Bird { void fly() { // Code for flying } } class Penguin extends Bird { // Penguins cannot fly @Override void fly() { throw new UnsupportedOperationException("Penguins cannot fly."); } }
In this example, the
Penguin
class violates LSP as it throws an exception for thefly
method, which is not expected behavior for a subtype ofBird
. Instead, we should design the hierarchy to ensure substitutability.// Good Design - Subtypes are substitutable interface Bird { void fly(); } class Sparrow implements Bird { @Override void fly() { // Code for flying like a sparrow } } class Penguin implements Bird { @Override void fly() { // Penguins cannot fly, but the method is still defined System.out.println("Penguins cannot fly."); } }
Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use. Keep interfaces small and focused.
Example: // Bad Design - A large interface forcing clients to implement unnecessary methods interface Printer { void print(); void scan(); void fax(); } class LaserPrinter implements Printer { // Need to implement all three methods, even though LaserPrinter cannot scan and fax }
In this example, the
Printer
interface is too large and forces clients to implement unnecessary methods. Instead, we should create smaller interfaces.//Good Design - Smaller interfaces for specific functionality interface Printer { void print(); } interface Scanner { void scan(); } interface FaxMachine { void fax(); } class LaserPrinter implements Printer { // Only need to implement print() for LaserPrinter }
Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions.
No comments:
Post a Comment