The Ultimate Guide to
S.O.L.I.D.
Principles
SOLID Principles in
Software Development
SOLID principles for software design were given by
Robert J. Martin (Uncle Bob) and Michael Feathers.
Promote clean, maintainable, and testable code.
Purpose
s Create understandable, readable, and
testable codew
s Facilitate collaboration among developers.
S .O. L . I . D
Learn more about Bosscoder Academy 01
SOLID Principles
Single Responsibility (SRPd
> Classes have one responsibility]
> Encourages focused classes for easier maintenance.
Open-Closed (OCPd
> Software entities open for extension, closed for modification]
> Facilitates code extension without altering existing code.
Liskov Substitution (LSPd
> Subtypes are substitutable for base types]
> Ensures expected behavior in derived classes.
Interface Segregation (ISPd
> Clients don't depend on unused interfaces]
> Promotes smaller, specialized interfaces to avoid unnecessary
dependencies.
Dependency Inversion (DIPd
> High-level modules depend on abstractions]
> Encourages flexibility and easy testing.
Learn more about Bosscoder Academy 02
Single Responsibility
Principle (SRP)
It states that a class should have only one reason to change, meaning
it should have a single responsibility.
@ Focusing on a single responsibility makes code easier to maintain,
understand, and test0
@ Prevents a class from becoming overly complex or bloated with
unrelated functionality.
Learn more about Bosscoder Academy 03
Suppose we have an EmailSender class responsible for sending
emails.
The EmailSender class handles email composition, user
authentication, and email sending.
We refactor the class to split responsibilitiesS
K\ EmailComposer: Responsible for composing email content=
`\ UserAuthenticator: Handles user authentication=
d\ EmailSender: Focuses on sending emails.
t Improved Maintainability: Changes to one responsibility won't
affect the others=
t Better Readability: Easier to understand each component's role=
t Efficient Testing: Isolating components simplifies unit testing.
Learn more about Bosscoder Academy 04
Open-Closed Principle
(OCP)
It suggests that software entities (classes, modules) should be open
for extension but closed for modification.
New functionality should be incorporable without requiring
modifications to the existing code within a class.
Modifying existing, well-tested code introduces the risk of
introducing bugs, which should be avoided whenever possible.
R OCP promotes software that can be easily extended with new
features without changing existing code6
R It reduces the risk of introducing new bugs when making
modifications.
Imagine you have a system that calculates the area of various shapes,
such as rectangles and circles.
You want to follow the OCP to allow for easy extension with new
shapes without modifying the existing code.
Learn more about Bosscoder Academy 05
Start with an abstract Shape class representing the common
properties and methods of all shapes:
abstract class Shape {
public abstract double area();
Implement concrete shapes like Rectangle and Circle by
extending the Shape class:
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
[Link] = width;
[Link] = height;
@Override
public double area() {
return width * height;
class Circle extends Shape {
private double radius;
public Circle(double radius) {
[Link] = radius;
@Override
public double area() {
return [Link] * radius * radius;
Learn more about Bosscoder Academy 06
Now, if you want to add a new shape, such as a triangle, you can do
so without modifying the existing code. You create a new Triangle
class that extends Shape:
class Triangle extends Shape {
private double base;
private double height;
public Triangle(double base, double height) {
[Link] = base;
[Link] = height;
@Override
public double area() {
return 0.5 * base * height;
By following the Open-Closed Principle, you can easily add new
shapes to your system without altering the existing code that deals
with calculating areas.
x Extensibility: Easily add new features without altering existing
code
x Reduced Risk: Modifying existing code can introduce bugs, and
OCP helps minimize this risk
x Maintainability: Code remains stable and easier to manage over
time.
Learn more about Bosscoder Academy 07
Liskov Substitution
Principle (LSP)
It emphasizes that objects of derived classes must be substitutable for
objects of their base classes without affecting the program's
correctness.
When class B inherits from class A as a subclass, it should seamlessly
work as a substitute for class A in any method that expects an object
of class A.
> LSP ensures that a derived class doesn't violate the expected
behavior of the base class9
> It maintains consistency in polymorphic behavior, improving code
reliability.
Let's consider an example using a classic geometric shape hierarchy:
java
class Shape {
public double getArea() {
return 0.0;
Learn more about Bosscoder Academy 08
class Circle extends Shape {
private double radius;
public Circle(double radius) {
[Link] = radius;
@Override
public double getArea() {
return [Link] * radius * radius;
class Square extends Shape {
private double side;
public Square(double side) {
[Link] = side;
@Override
public double getArea() {
return side * side;
In this exampleR
SP The Shape class is the base class, defining a getArea method
that returns 0 as a default implementation.
6P Circle and Square are subclasses that extend Shape and
provide their own implementations of getArea. These
implementations calculate the area of a circle and a square,
respectively.
Learn more about Bosscoder Academy 09
Here's how LSP is applied:
Here's how LSP is applied:
Shape circleShape = new Circle(5.0);
Shape squareShape = new Square(4.0);
double circleArea = [Link]();
double squareArea = [Link]();
[Link]("Circle Area: " + circleArea);
[Link]("Square Area: " + squareArea);
In this code, we create instances of Circle and Square but store
them in variables of type Shape, the base class. The Liskov
Substitution Principle ensures that we can use these subclasses
wherever a Shape is expected.
} Polymorphic Behavior: LSP ensures that derived classes behave
as expected when used polymorphicallyi
} Consistency: Code relying on base classes can work with derived
classes seamlesslyi
} Reduced Bugs: Improved program correctness leads to fewer
unexpected issues.
Learn more about Bosscoder Academy 10
Interface Segregation
Principle (ISP)
It emphasizes that clients should not be forced to depend on
interfaces they do not use.
6 ISP reduces unnecessary dependencies, leading to more cohesive
and maintainable code)
6 It ensures that clients only implement the methods they require,
improving code clarity.
“Clients should not be forced to depend upon interfaces that
they don't use”
Good
“Segregate your interfaces”
Learn more about Bosscoder Academy 11
Let's consider an example in the context of a software application for
a document management system. Suppose we have an interface
called Document that initially includes methods for creating, editing,
and sharing documents:
public interface Document {
void create();
void edit();
void share();
However, in our application, we have two types of documents:
TextDocument and SpreadsheetDocument. While the
TextDocument class can implement all the methods in the
Document interface, the SpreadsheetDocument class doesn't need
the edit method since spreadsheets don't have a typical "editing"
action. This violates the ISP because it forces the
SpreadsheetDocument class to implement methods it doesn't need.
To adhere to the ISP, we should refactor the interface to make it more
granular, like this:
public interface Createable {
void create();
public interface Editable {
void edit();
Learn more about Bosscoder Academy 12
public interface Shareable {
void share();
Now, our classes can implement only the interfaces that are relevant
to them:
public class TextDocument implements Createable,
Editable, Shareable {
// Implement the methods for text documents.
public class SpreadsheetDocument implements Createable,
Shareable {
// Implement the methods for spreadsheet documents.
By adhering to the ISP, we've created smaller, more focused interfaces
that allow classes to implement only what they need, reducing
unnecessary dependencies and making the code more maintainable
and flexible.
Reduced Dependencies: Clients depend only on the interfaces
they need, reducing unnecessary coupling
Improved Clarity: Code becomes more self-explanatory as clients
implement interfaces that align with their specific functionality
Easier Maintenance: Changes or additions to interfaces impact
only relevant clients, reducing ripple effects
Learn more about Bosscoder Academy 13
Dependency Inversion
Principle (DIP)
It states that high-level modules should not depend on low-level
modules, but both should depend on abstractions. In other words, the
details should depend on abstractions, not the other way around.
DIP promotes loose coupling between components, making software
more flexible and maintainable. It enables the creation of
interchangeable, pluggable components that can be easily
substituted without affecting the overall system.
"Higher modules should not depend on lower modules. In such
cases invert the dependency"
Dependency Inverted
Learn more about Bosscoder Academy 14
Consider a messaging system that sends notifications through various
channels: email, SMS, and push notifications.
Violation of DIP Without following DIP, the high-level
NotificationService class directly depends on low-level
implementations:
class NotificationService {
private EmailSender emailSender;
private SMSSender smsSender;
private PushNotificationSender pushSender;
public NotificationService() {
emailSender = new EmailSender();
smsSender = new SMSSender();
pushSender = new PushNotificationSender();
public void sendEmail() {
[Link]();
public void sendSMS() {
[Link]();
public void sendPushNotification() {
[Link]();
Learn more about Bosscoder Academy 15
Applying DIP Following DIP, we use abstractions and interfaces to
invert the dependencies:
interface MessageSender {
void send();
class EmailSender implements MessageSender {
@Override
public void send() {
// Send email
class SMSSender implements MessageSender {
@Override
public void send() {
// Send SMS
class PushNotificationSender implements MessageSender {
@Override
public void send() {
// Send push notification
class NotificationService {
private MessageSender sender;
public NotificationService(MessageSender sender) {
[Link] = sender;
public void send() {
[Link]();
Learn more about Bosscoder Academy 16
By following the Dependency Inversion Principle, the
NotificationService is no longer tightly coupled to specific sender
implementations.
You can easily add new sender types without modifying the service,
promoting flexibility and maintainability.
: Flexibility: Easily swap or extend components without affecting
high-level modules'
: Scalability: Allows for the addition of new features or integrations
with minimal code changes'
: Testability: Simplifies unit testing by enabling the use of mock or
stub implementations for testing.
Learn more about Bosscoder Academy 17
Why
Bosscoder?
1000+ Alumni placed at Top
Product-based companies.
More than 136% hike for every
2 out of 3 working professional.
Average package of 24LPA.
Explore More