SOLID is an acronym that represents a set of five design principles in object-oriented programming and software design. These principles aim to create more maintainable, flexible, and scalable software by promoting a modular and clean code structure. The SOLID principles were introduced by Robert C. Martin and have become widely adopted in the software development industry. Here’s a brief overview of each principle:
Single Responsibility Principle (SRP):
- A class should have only one reason to change, meaning that it should have only one responsibility or job.
- This principle encourages the separation of concerns and helps to ensure that a class is focused on doing one thing well.
// Before SRP
class Report {
public void generateReport() {
// code for generating the report
}
public void saveToFile() {
// code for saving the report to a file
}
}
// After SRP
class Report {
public void generateReport() {
// code for generating the report
}
}
class ReportSaver {
public void saveToFile(Report report) {
// code for saving the report to a file
}
}
Open/Closed Principle (OCP):
- Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
- This encourages developers to add new functionality through the creation of new classes or modules rather than altering existing ones.
// Before OCP
class Rectangle {
public double width;
public double height;
}
class AreaCalculator {
public double calculateArea(Rectangle rectangle) {
return rectangle.width * rectangle.height;
}
}
// After OCP
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
private double width;
private double height;
// constructor and other methods
@Override
public double calculateArea() {
return width * height;
}
}
class Circle implements Shape {
private double radius;
// constructor and other methods
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
Liskov Substitution Principle (LSP):
- Subtypes should be substitutable for their base types without altering the correctness of the program.
- This principle ensures that objects of a derived class can be used in place of objects of the base class without affecting the program’s functionality.
// Before LSP
class Bird {
public void fly() {
// code for flying
}
}
class Ostrich extends Bird {
// Ostrich is a bird, but it can't fly
}
// After LSP
interface FlyingBird {
void fly();
}
class Sparrow implements FlyingBird {
@Override
public void fly() {
// code for flying
}
}
class Ostrich {
// Ostrich doesn't implement FlyingBird, as it can't fly
}
Interface Segregation Principle (ISP):
- Clients should not be forced to depend on interfaces they do not use.
- It advocates for the creation of small, specific interfaces rather than large, general-purpose ones, to avoid clients being forced to implement methods they don’t need.
// Before ISP
interface Worker {
void work();
void eat();
void sleep();
}
class Engineer implements Worker {
@Override
public void work() {
// code for working
}
@Override
public void eat() {
// code for eating
}
@Override
public void sleep() {
// code for sleeping
}
}
// After ISP
interface Workable {
void work();
}
interface Eatable {
void eat();
}
interface Sleepable {
void sleep();
}
class Engineer implements Workable, Eatable, Sleepable {
@Override
public void work() {
// code for working
}
@Override
public void eat() {
// code for eating
}
@Override
public void sleep() {
// code for sleeping
}
}
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 principle promotes the use of abstractions (like interfaces or abstract classes) to decouple high-level and low-level modules, making the system more flexible and easier to change.
// Before DIP
class LightBulb {
public void turnOn() {
// code for turning on the light bulb
}
public void turnOff() {
// code for turning off the light bulb
}
}
class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
// code for operating the switch
if (/* some condition */) {
bulb.turnOn();
} else {
bulb.turnOff();
}
}
}
// After DIP
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
@Override
public void turnOn() {
// code for turning on the light bulb
}
@Override
public void turnOff() {
// code for turning off the light bulb
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
// code for operating the switch
if (/* some condition */) {
device.turnOn();
} else {
device.turnOff();
}
}
}
Adhering to SOLID principles can result in code that is easier to understand, maintain, and extend. These principles contribute to the overall goal of creating robust and scalable software systems.