DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Implementing SOLID Principles in Android Development
  • Unlocking AI Coding Assistants Part 3: Generating Diagrams, Open API Specs, And Test Data
  • Soft Skills Are as Important as Hard Skills for Developers
  • Practical Coding Principles for Sustainable Development

Trending

  • From Fragmentation to Focus: A Data-First, Team-First Framework for Platform-Driven Organizations
  • AI-Assisted Coding for iOS Development: How Tools like CursorAI Are Redefining the Developer Workflow
  • Scalability 101: How to Build, Measure, and Improve It
  • Zero Trust Isn't Just for Networks: Applying Zero-Trust Principles to CI/CD Pipelines
  1. DZone
  2. Culture and Methodologies
  3. Methodologies
  4. SOLID Principle Simplified

SOLID Principle Simplified

SOLIDified: Demystifying Software Design Principles with practical examples to write maintainable, extensible, and testable code for complex software systems.

By 
Lalithkumar Prakashchand user avatar
Lalithkumar Prakashchand
·
Jun. 20, 24 · Tutorial
Likes (8)
Comment
Save
Tweet
Share
10.0K Views

Join the DZone community and get the full member experience.

Join For Free

As software systems grow in complexity, it becomes increasingly important to write maintainable, extensible, and testable code. The SOLID principles, introduced by Robert C. Martin (Uncle Bob), are a set of guidelines that can help you achieve these goals. These principles are designed to make your code more flexible, modular, and easier to understand and maintain. In this post, we’ll explore each of the SOLID principles with code examples to help you understand them better.

1. Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change. In other words, a class should have a single responsibility or job. By adhering to this principle, you can create classes that are easier to understand, maintain, and test.

Example

Imagine we have a UserManager class that handles user registration, password reset, and sending notifications. This class violates the SRP because it has multiple responsibilities.

Java
 
public class UserManager {
    public void registerUser(String email, String password) {
        // Register user logic
    }

    public void resetPassword(String email) {
        // Reset password logic
    }

    public void sendNotification(String email, String message) {
        // Send notification logic
    }
}


To adhere to the SRP, we can split the responsibilities into separate classes:

Java
 
public class UserRegistration {
    public void registerUser(String email, String password) {
        // Register user logic
    }
}

public class PasswordReset {
    public void resetPassword(String email) {
        // Reset password logic
    }
}

public class NotificationService {
    public void sendNotification(String email, String message) {
        // Send notification logic
    }
}


2. Open/Closed Principle (OCP)

The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In simpler terms, you should be able to add new functionality without modifying the existing code.

Example

Let’s consider a ShapeCalculator class that calculates the area of different shapes. Without adhering to the OCP, we might end up with a lot of conditional statements as we add support for new shapes.

Java
 
public class ShapeCalculator {
    public double calculateArea(Shape shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        }
        // More conditional statements for other shapes
        return 0;
    }
}


To adhere to the OCP, we can introduce an abstract Shape class and define the calculateArea method in each concrete shape class.

Java
 
public abstract class Shape {
    public abstract double calculateArea();
}

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}


Now, we can add new shapes without modifying the ShapeCalculator class.

3. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that subtypes must be substitutable for their base types. In other words, if you have a base class and a derived class, you should be able to use instances of the derived class wherever instances of the base class are expected, without affecting the correctness of the program.

Example

Let’s consider a Vehicle class and a Car class that extends Vehicle. According to the LSP, any code that works with Vehicle objects should also work correctly with Car objects.

Java
 
public class Vehicle {
    public void startEngine() {
        // Start engine logic
    }
}

public class Car extends Vehicle {
    @Override
    public void startEngine() {
        // Additional logic for starting a car engine
        super.startEngine();
    }
}


In this example, the Car class overrides the startEngine method and extends the behavior by adding additional logic. However, if we violate the LSP by changing the behavior in an unexpected way, it could lead to issues.

Java
 
public class Car extends Vehicle {
    @Override
    public void startEngine() {
        // Different behavior, e.g., throwing an exception
        throw new RuntimeException("Engine cannot be started");
    }
}


Here, the Car class violates the LSP by throwing an exception instead of starting the engine. This means that code that expects to work with Vehicle objects may break when Car objects are substituted.

4. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that clients should not be forced to depend on interfaces they don’t use. In other words, it’s better to have many smaller, focused interfaces than a large, monolithic interface.

Example

Imagine we have a Worker interface that defines methods for different types of workers (full-time, part-time, contractor).

Java
 
public interface Worker {
    void calculateFullTimeSalary();
    void calculatePartTimeSalary();
    void calculateContractorHours();
}

public class FullTimeEmployee implements Worker {
    @Override
    public void calculateFullTimeSalary() {
        // Calculate full-time salary
    }

    @Override
    public void calculatePartTimeSalary() {
        // Not applicable, throw exception or leave empty
    }

    @Override
    public void calculateContractorHours() {
        // Not applicable, throw exception or leave empty
    }
}


In this example, the FullTimeEmployee class is forced to implement methods it doesn't need, violating the ISP. To address this, we can segregate the interface into smaller, focused interfaces:

Java
 
public interface FullTimeWorker {
    void calculateFullTimeSalary();
}

public interface PartTimeWorker {
    void calculatePartTimeSalary();
}

public interface ContractWorker {
    void calculateContractorHours();
}

public class FullTimeEmployee implements FullTimeWorker {
    @Override
    public void calculateFullTimeSalary() {
        // Calculate full-time salary
    }
}


Now, each class only implements the methods it needs, adhering to the ISP.

5. Dependency Inversion Principle (DIP)

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions.

Example

Let’s consider a UserService class that depends on a DatabaseRepository class for data access.

Java
 
public class UserService {
    private DatabaseRepository databaseRepository;

    public UserService() {
        databaseRepository = new DatabaseRepository();
    }

    public void createUser(String email, String password) {
        // Use databaseRepository to create a new user
    }
}


In this example, the UserService class is tightly coupled to the DatabaseRepository class, violating the DIP. To adhere to the DIP, we can introduce an abstraction (interface) and inject the implementation at runtime.

Java
 
public interface Repository {
    void createUser(String email, String password);
}

public class DatabaseRepository implements Repository {
    @Override
    public void createUser(String email, String password) {
        // Database logic to create a new user
    }
}

public class UserService {
    private Repository repository;

    public UserService(Repository repository) {
        this.repository = repository;
    }

    public void createUser(String email, String password) {
        repository.createUser(email, password);
    }
}


Now, the UserService class depends on the Repository abstraction, and the implementation (DatabaseRepository) can be injected at runtime. This makes the code more modular, testable, and easier to maintain.

By following the SOLID principles, you can create more maintainable, extensible, and testable code. These principles promote modular design, loose coupling, and separation of concerns, ultimately leading to better software quality and easier maintenance over time.

Dependency inversion principle Liskov substitution principle Coding (social sciences)

Published at DZone with permission of Lalithkumar Prakashchand. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Implementing SOLID Principles in Android Development
  • Unlocking AI Coding Assistants Part 3: Generating Diagrams, Open API Specs, And Test Data
  • Soft Skills Are as Important as Hard Skills for Developers
  • Practical Coding Principles for Sustainable Development

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!