Sitemap
Press enter or click to view image in full size

Understanding SOLID Principles

--

As software systems get bigger and more complex, it becomes very important to write clean and easy-to-maintain code. Developers use a set of rules called SOLID principles to help make software easier to understand, test, maintain, and improve. These principles were created by Robert C. Martin (Uncle Bob) and are commonly used in object-oriented programming languages like Java, C#, and C++.

What is SOLID ?

SOLID =

S → Single Responsibility Principle (SRP)

O → Open/Closed Principle (OCP)

L → Liskov Substitution Principle (LSP)

I → Interface Segregation Principle (ISP)

Get Dharaka Deshan’s stories in your inbox

Join Medium for free to get updates from this writer.

D → Dependency Inversion Principle (DIP)

1. Single Responsibility Principle

The Single Responsibility Principle states that a class should have only one responsibility or one reason to change.

A class should perform only one specific task. If a class handles multiple responsibilities, it becomes difficult to maintain and modify. Separating responsibilities makes the code cleaner and easier to understand.

Example —

Instead of creating one Student class that stores student information, saves data, prints reports, and sends emails, these tasks should be divided into separate classes.

Bad Example

//Here one class is doing many jobs (not recommended):
class Student {
String name;

void saveToDatabase() {
System.out.println("Saving student...");
}

void sendEmail() {
System.out.println("Sending email...");
}

void printReport() {
System.out.println("Printing report...");
}
}

//Problem:
//One class handles database, email, and report
//Hard to maintain and change

Good Example

//  Each class has only one responsibility

class Student {
String name;
}

class StudentRepository {
void save(Student student) {
System.out.println("Saving student...");
}
}

class EmailService {
void sendEmail(Student student) {
System.out.println("Sending email...");
}
}

class ReportPrinter {
void print(Student student) {
System.out.println("Printing report...");
}
}

2. Open/Closed Principle

The Open/Closed Principle states that software entities should be open for extension but closed for modification.

Instead of changing existing code whenever a new feature is added, developers should extend the application by creating new classes.

Bad Example

class Payment {

public void pay(String method){

if(method.equals("Cash")){
System.out.println("Cash Payment");
}

else if(method.equals("Card")){
System.out.println("Card Payment");
}
}
}

// imagine Now customer wants add
// paypal , apple pay
// then must keep changin this class and its Not Good.

Good Example

// Create an interface
interface Payment {
void pay();
}

// Cash payment
class CashPayment implements Payment {

public void pay(){
System.out.println("Cash Payment");
}
}

//Card Payment

class CardPayment implements Payment {

public void pay(){
System.out.println("Card Payment");
}
}

// in here Old code never changes. Only new classes are added.

3. Liskov Substitution Principle

The Liskov Substitution Principle states that objects of a subclass should be replaceable with objects of the parent class without affecting program correctness.

A child class should behave correctly wherever its parent class is expected.

in simple : Anywhere you use a parent object, you should also be able to use its child object

Bad Example

class Bird{

public void fly(){
System.out.println("Flying");
}

public void walk(){
System.out.println("walking");
}
}

class Penguin extends Bird{

@Override
public void walk(){
System.out.println("Penguins can walk");
}

@Override
public void fly(){
throw new UnsupportedOperationException();
}
}

// Penguins cannot fly.

// So Penguin should NOT inherit Bird if Bird promises flying.

// This breaks Liskov Substitution Principle.

Good Example

class Bird {

puublic void walk(){
System.out.println("walking");
}
}

// Only birds that can fly extend FlyingBird
class FlyingBird extends Bird {

public void fly() {
System.out.println("Flying");
}
}

// Sparrow can fly
class Sparrow extends FlyingBird {

@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}

// Penguin cannot fly, so it extends Bird only
class Penguin extends Bird {

@Override
public void walk() {
System.out.println("Penguin is walking");
}
}

4. Interface Segregation Principle

The Interface Segregation Principle states that a class should not be forced to implement methods it does not use.

Large interfaces should be divided into smaller, more specific interfaces.

Bad Example

interface Worker{

void work();

void eat();

void sleep();
}


class Robot implements Worker{

public void work(){
System.out.println("Working");
}

public void eat(){
}

public void sleep(){
}
}

// in here Robot doesn't eat or sleep.

Good Example

// Split interfaces
interface Workable{

void work();
}

interface Eatable{

void eat();
}


// Human
class Human implements Workable,Eatable{

public void work(){
System.out.println("Working");
}

public void eat(){
System.out.println("Eating");
}
}


// Robot
class Robot implements Workable{

public void work(){
System.out.println("Working");
}
}


// The Robot class only implements the methods it actually needs.
// Now code is Much cleaner.

5. Dependency Inversion Principle

The Dependency Inversion Principle states that high-level modules should depend on abstractions rather than concrete implementations.(High-level classes should not depend directly on low-level classes)

Instead of directly using a specific class, programs should depend on interfaces. This makes it easier to replace implementations without changing business logic.

Bad Example

// DB
class MySQLDatabase{

public void connect(){
System.out.println("Connected to MySQL");
}
}

// StudentService file
class StudentService{

MySQLDatabase db = new MySQLDatabase();

public void save(){
db.connect();
}
}

// Problem

// Later you change to PostgreSQL.

// You must modify StudentService.

Good Example

//Create interface
interface Database{

void connect();
}

//PostgreSQL

class PostgreSQLDatabase implements Database{

public void connect(){
System.out.println("Connected PostgreSQL");
}
}

// Student Service file

class StudentService{

private Database database;

public StudentService(Database database){
this.database = database;
}

public void save(){
database.connect();
}
}

// .No changes to StudentService.

Advantages of SOLID Principles

Applying SOLID principles provides several advantages

  • Improves code readability.
  • Makes software easier to maintain.
  • Reduces code duplication.
  • Simplifies testing.
  • Supports future enhancements.
  • Encourages reusable components.
  • Produces flexible and scalable applications.

--

--