Hello guys, Today I'm going to explain SOLID software engineering principle with TypeScript sample codes
SOLID is an acronym that stands for the five principles of object-oriented design. These principles were first introduced by Robert C. Martin in his book "Agile Software Development, Principles, Patterns, and Practices." The SOLID principles are:
S - Single Responsibility Principle
O - Open-Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Single Responsibility Principle
The basic meaning of this principle is a class should have only one reason to change. or the class should have only one responsibility.
class User {
constructor(private readonly name: string, private readonly email: string) {}
public getName(): string {
return this.name;
}
public getEmail(): string {
return this.email;
}
}
class UserService {
constructor(private readonly userRepository: UserRepository) {}
public createUser(name: string, email: string): void {
const user = new User(name, email);
this.userRepository.save(user);
}
}
In this example, the User
class has a single responsibility: to represent a user with a name and an email. The UserService
class has a single responsibility: to create and save a new user using the UserRepository.
Open-Closed Principle
According to this principle, every software entity ( classes, modules, functions, ...) should be open for extinction but closed for modifications, because if someone modify the entity that can be break the application it is a risk
interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
public area(): number {
return this.width * this.height;
}
}
class Circle implements Shape {
constructor(private radius: number) {}
public area(): number {
return Math.PI * this.radius ** 2;
}
}
class AreaCalculator {
constructor(private shapes: Shape[]) {}
public sum(): number {
return this.shapes.reduce((acc, shape) => acc + shape.area(), 0);
}
}
In this example, the Shape
interface is open for extension (you can implement it to create new shapes) but closed for modification (you cannot modify the area
method). The AreaCalculator
class is also closed for modification since it uses the area
method of the Shape
interface without needing to know the specific implementation.
Liskov Substitution Principle
The basic idea of this is subtypes must be substitutable for their base types.
class Rectangle {
constructor(private width: number, private height: number) {}
public setWidth(width: number): void {
this.width = width;
}
public setHeight(height: number): void {
this.height = height;
}
public getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
constructor(size: number) {
super(size, size);
}
public setWidth(width: number): void {
this.width = width;
this.height = width;
}
public setHeight(height: number): void {
this.width = height;
this.height = height;
}
}
In this example, the Square
class extends the Rectangle
class, but it violates the Liskov Substitution Principle because it overrides the setWidth
and setHeight
methods in a way that changes the behavior of the getArea
method. A client that uses the Rectangle
class might expect the getArea
method to always return the correct result, but this is not guaranteed when using a Square
object.
Interface Segregation Principle
The Interface Segregation Principle (ISP) is a principle of object-oriented design that states that clients (classes or components that use other classes or components) should not be forced to depend on interfaces they do not use.
In other words, a client should not be required to implement methods or properties that it does not need. This can lead to "fat" interfaces that are unnecessarily complex and hard to implement.
Here is an example of how the Interface Segregation Principle can be applied in TypeScript:
interface Animal {
eat(): void;
sleep(): void;
}
interface Feline extends Animal {
meow(): void;
}
interface Canine extends Animal {
bark(): void;
}
class Cat implements Feline {
public eat(): void {
console.log('Eating...');
}
public sleep(): void {
console.log('Sleeping...');
}
public meow(): void {
console.log('Meowing...');
}
}
class Dog implements Canine {
public eat(): void {
console.log('Eating...');
}
public sleep(): void {
console.log('Sleeping...');
}
public bark(): void {
console.log('Barking...');
}
}
In this example, the Feline
and Canine
interfaces are more specific versions of the Animal
interface that only includes the methods that are relevant to felines and canines, respectively. This allows the Cat
and Dog
classes to implement only the methods that they need, without being forced to implement unnecessary methods.
Dependency Inversion Principle
Dependency inversion is a principle that states that high-level modules (components or classes that provide complex functionality) should not depend on low-level modules (components or classes that provide simple, underlying functionality). Instead, both should depend on abstractions.
This principle is often referred to as the "Dependency Inversion Principle" (DIP) and is one of the SOLID principles of object-oriented design. It is meant to decouple the design of a software system, making it more flexible and maintainable.
interface Engine {
start(): void;
stop(): void;
}
class Car {
constructor(private readonly engine: Engine) {}
public start(): void {
this.engine.start();
}
public stop(): void {
this.engine.stop();
}
}
class GasEngine implements Engine {
public start(): void {
console.log('Starting gas engine...');
}
public stop(): void {
console.log('Stopping gas engine...');
}
}
class ElectricEngine implements Engine {
public start(): void {
console.log('Starting electric engine...');
}
public stop(): void {
console.log('Stopping electric engine...');
}
}
In this example, the Car
class depends on the Engine
interface and does not care about the specific implementation of the engine. This allows the Car
class to be flexible and easily extensible, as it can work with any type of engine as long as it implements the Engine
interface.
These principles are meant to guide the design of software systems in a way that makes them more flexible, maintainable, and scalable. Adhering to the SOLID principles can help you create a codebase that is easier to understand, modify, and test.
One extra kiss from me,
KISS
KISS is an acronym that stands for "Keep It Simple, Stupid." It is a design principle that suggests that systems should be designed to be as simple as possible and that unnecessary complexity should be avoided. The idea behind the KISS principle is that simple systems are easier to use, understand, and maintain than complex ones. Some common applications of the KISS principle include software design, engineering, and project management.