The SOLID principles are five design principles that help software developers create maintainable, understandable, and simple software systems. Robert C. McCarthy developed these principles. Introduced by Martin (Uncle Bob), they became a cornerstone in accessories. Let’s explore each principle and how it applies to Java programming,
Single Responsibility Principle (SRP)
The principle of monopoly states that a class should have only one reason to change, that is, only one function or responsibility. This principle promotes synchronization and reduces coupling in software systems.
Example-
// Example of a class violating SRP
class User {
private String username;
private String email;
public void createUser(String username, String email) {
// Code to create a user in the database
}
public void sendEmail(String message) {
// Code to send an email to the user
}
}
In the example above,
The User
class violates SRP because it has two responsibilities: managing user data and sending emails. It is best to divide these responsibilities into two different classes,
// Example of refactored classes following SRP
class User {
private String username;
private String email;
// Getters and setters
}
class UserManager {
public void createUser(String username, String email) {
// Code to create a user in the database
}
}
class EmailService {
public void sendEmail(String to, String message) {
// Code to send an email
}
}
Open/Closed Principle (OCP)
The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open to extension but closed to modification. This means that you can extend the statement without changing the source code of a class.
Example
// Example of a class violating OCP
class Shape {
public void draw() {
// Code to draw a shape
}
}
// If we want to add new shapes, we need to modify Shape class
Instead, use interfaces and properties to achieve OCP,
// Example following OCP
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
// Code to draw a circle
}
}
class Square implements Shape {
@Override
public void draw() {
// Code to draw a square
}
}
Liskov Substitution Principle (LSP)
The Liskov substitution principle states that objects of a superclass can be substituted without affecting the validity of the system. In other words, subclasses must be able to extend and specialize behavior without compromising the functionality that implements the superclass.
Example
// Example violating LSP
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
The Square
class violates LSP because it modifies the behavior of the
setWidth
and setHeight
methods derived from Rectangle
, which results in unexpected behavior when
Square
objects are used in the context of a Rectangle
object.
Interface Segregation Principle (ISP)
Interface Separation Principle (ISP) .
The interface partitioning principle states that clients are not forced to rely on unused interfaces. Several smaller networks based on specific customer needs should be preferred to one large network.
Example
// Example violating ISP
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
@Override
public void work() {
// Code for robot to work
}
@Override
public void eat() {
// No action for robot to eat
}
}
Here the Robot
class is forced to use the redundant eat()
method. Separate interfaces can be used instead,
interface Worker {
void work();
}
interface Eater {
void eat();
}
class Robot implements Worker {
@Override
public void work() {
// Code for robot to work
}
}
Dependency Inversion Principle (DIP)
Dependency Inversion theory states that a higher-level module should not depend on a lower-level module. Both may rely on an abstraction (interface). Moreover, abstractions should not be based on details; The details (concrete implementation) can depend on the abstraction.
Example
// Example violating DIP
class LightBulb {
public void turnOn() {
// Code to turn on the light bulb
}
}
class Switch {
private LightBulb bulb;
public Switch() {
this.bulb = new LightBulb();
}
public void flip() {
bulb.turnOn();
}
}
Instead, turn on the dependency by using interfaces,
interface Switchable {
void turnOn();
}
class LightBulb implements Switchable {
@Override
public void turnOn() {
// Code to turn on the light bulb
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void flip() {
device.turnOn();
}
}
Applying these SOLID principles to Java helps create modular, maintainable, scalable code. Each principle addresses a specific aspect of software design, promoting good code design, reusability, and long-term ease of maintenance.
Also, Read: Explain the Collections in Java
Leave Comment