0% found this document useful (0 votes)
32 views19 pages

Understanding SOLID Principles in Java

The document provides an overview of the SOLID principles of software design including Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. Examples are given for each principle to illustrate how it can be applied to improve code quality.

Uploaded by

Eltaif Hadil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views19 pages

Understanding SOLID Principles in Java

The document provides an overview of the SOLID principles of software design including Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. Examples are given for each principle to illustrate how it can be applied to improve code quality.

Uploaded by

Eltaif Hadil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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

You might also like