CategoryTagArticle

admin

I'm a Full-stack developer

Tag

Linked List
Data Structure
Chat GPT
Design Pattern
Microservices
API
AWS CDK
ReactJS
AWS Lightsail
Flutter Mobile
What are the SOLID principles?
Published date: 17/06/2023

If we want to have good software, the infrastructure should be good first. We should learn more techniques that help to have better quality. Firstly, we have to know SOLID principles.


What are the SOLID principles?


SOLID principles are object-oriented design concepts relevant to software development. SOLID is an acronym for five other class-design principles. SOLID helps developers to write code that is easy to read, easy to maintain, and easy to understand.

Those 5 principles include:


  • Single responsibility principle (SRP)
  • Open/Closed principle (OCP)
  • Liskov substitution principle (LSP)
  • Interface segregation principle (ISP)
  • Dependency inversion principle (DIP)


Single responsibility principle


Each class should be responsible for a single part or functionality of the system.

We have an example that violates this principle like the code below:

export class DbConnection {
  connect(): Promise<DataSource> {
    try {
      const con = new DataSource({});

      return Promise.resolve(con);
    } catch (error) {
      winston.createLogger({
        level: 'debug',
        format: ...,
        transports: []
      });

      return Promise.reject(error);
    }
  }
}


This is violated because DbConnection class both connects to DB and writes an error log.

To avoid violating this principle, we need to separate into 2 classes DbConnection and Logger.

export class DbConnection {
  private Logger _logger;

  constructor() {
    super();

    this._logger = new Logger();
  }

  connect(): Promise<DataSource> {
    try {
      const con = new DataSource({});

      return Promise.resolve(con);
    } catch (error) {
      this._logger.writeError(error.message);

      return Promise.reject(error);
    }
  }
}

export class Logger {
  void writeError(message: string) {
    winston.createLogger({
      level: 'debug',
      format: ...,
      transports: []
    });
  }
}


Open/Closed principle

Software components should be open for extension, but not for modification.

To make it easier to understand, let's go through an example:

export class Staff {
  calculateSalary(staff: StaffEntity) {
    const baseSalary = this.getBaseSalary(staff);

    switch(staff.position) {
      case 'developer':
        return baseSalry * 1.5;
      case 'product owner':
        return baseSalry * 1.8;
      default:
        return baseSalary
    }
  }
}


Suppose we now want to add another subclass called 'solution architecture'. We would have to modify the above class by adding another switch case statement, which goes against the Open-Closed Principle.

A better approach would be for the subclasses Developer, ProductOwner, and SolutionArchitecture to override the calculateSalary method:


export class Staff {
  calculateSalary(staff: StaffEntity) {
    return this.getBaseSalary(staff);
    }
  }
}

export class Developer extends Staff {
  calculateSalary(staff: StaffEntity) {
    const baseSalary = this.getBaseSalary(staff);

    return baseSalary * 1.5;
    }
  }
}

export class ProductOwner extends Staff {
  calculateSalary(staff: StaffEntity) {
    const baseSalary = this.getBaseSalary(staff);

    return baseSalary * 1.8;
    }
  }
}

export class SolutionArchitecture extends Staff {
  calculateSalary(staff: StaffEntity) {
    const baseSalary = this.getBaseSalary(staff);

    return baseSalary * 1.95;
    }
  }
}


Adding another Staff Position type is as simple as making another subclass and extending from the Staff class.


Liskov substitution principle

Objects of its subclasses without breaking the system.

Consider a typical example of a Square derived class and Rectangle base class:

export class Rectangle {
    private double height;
    private double width;
    public void setHeight(double h) { height = h; }
    public void setWidht(double w) { width = w; }
}

export class Square extends Rectangle {}


By conventional logic, a square is a rectangle. So, logically, the Square class is born from the Rectangle class. But the square does not need both the length and the width because the length and width of the square are the same.

Obviously, this is a serious problem. However there is a way to solve it, we can override the setWidth and setHeight functions as follows.

export class Rectangle {
    private double height;
    private double width;
    public void setHeight(double h) { height = h; }
    public void setWidht(double w) { width = w; }
}

export class Square extends Rectangle {
    public void setHeight(double h) {
        super.setHeight(h);
        super.setWidth(h);
    }

    public void setWidth(double w) {
        super.setHeight(w);
        super.setWidth(w);
    }
}

Interface segregation principle


No client should be forced to depend on methods that it does not use.


Suppose there’s an interface for a vehicle and a Bike class:

export interface Vehicle {
    public void drive();
    public void stop();
    public void refuel();
    public void openDoors();
}

export class Bike implements Vehicle {
    // Can be implemented
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
    
    // Can not be implemented
    public void openDoors() {...}
}


The openDoors() method does not make sense for a Bike class because the bike does not any doors. To fix this, follow the code below.

export interface IBike {
    public void drive();
    public void stop();
    public void refuel();
}

export interface IVehicle {
    public void drive();
    public void stop();
    public void refuel();
    public void openDoors();
}

public class Bike implements IBike {
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
}

public class Vehicle implements IVehicle {
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
    public void openDoors() {...}
}


Dependency inversion principle

High-level modules should not depend on low-level modules, both should depend on abstractions.


export class DbConnection {
  private ErrorLogger _errorLogger = new ErrorLogger();

  connect(): Promise<DataSource> {
    try {
      const con = new DataSource({});

      return Promise.resolve(con);
    } catch (error) {
      this._errorLogger.write(error.message);

      return Promise.reject(error);
    }
  }
}


The code will work, for now, but what if we wanted to add another log type, let’s say a info log? This will require refactoring the DbConnection class.

Fix it through dependency injection.


export class DbConnection {
  private Logger _logger;

  constructor(logger: Logger) {
    super();

    this._logger = logger;
  }

  connect(): Promise<DataSource> {
    try {
      const con = new DataSource({});

      return Promise.resolve(con);
    } catch (error) {
      this._logger.write(error.message);

      return Promise.reject(error);
    }
  }
}


Now that the logger is no longer dependent on the DbConnection class, we can change it as we please by passing it into this class at initialization.


Conclusion

These are 5 essential principles used by professional developers around the globe, you should start applying them today!

Recommend

Part 1: How to deploy Ghost Blog on AWS using Lightsail
admin17/06/2023

Part 1: How to deploy Ghost Blog on AWS using Lightsail
In this article, I want to introduce about how to deploy Ghost Blog on AWS using Lightsail.
TypeScript Design Pattern - Adapter
admin08/08/2023

TypeScript Design Pattern - Adapter
This design pattern acts as a bridge between two different interfaces.
Part 3: React Fragments
admin18/06/2023

Part 3: React Fragments
In this part, I will show you about good benefits when using fragments in React.
Newest

How to integrate ChatGPT-3.5 Turbo into Node.js
admin10/01/2024

How to integrate ChatGPT-3.5 Turbo into Node.js
Step-by-Step Guide to Incorporating ChatGPT-3.5 Turbo into Node.js for Basic ReactJS Applications
Extract data using web scraping with Python
admin14/03/2024

Extract data using web scraping with Python
A beginner’s guide to learn web scraping with Python!
Semantic Versioning NodeJS
admin07/07/2023

Semantic Versioning NodeJS
How to Use Semantic Versioning in NPM
Đinh Thành Công Blog

My website, where I write blogs on a variety of topics and where I have some experiments with new technologies.

hotlinelinkedinskypezalofacebook
DMCA.com Protection Status
Feedback
Name
Phone number
Email
Content
Download app
hotline

copyright © 2023 - AGAPIFA

Privacy
Term
About