SOLID: What is? Why this? How use?

İntroduction:

Sami Salih İbrahimbaş
5 min readApr 29, 2022
solid photo

As software evolves, it needs to be managed, developed, and scaled, like everything else that evolves. The number of internet users and their needs are constantly increasing. This means that when you make an application used on the internet, it does not stop.

Applications on the internet will need to be constantly developed and regulated. Therefore, the program you make should be developed and open to new features, in short, it should be flexible.

Why Object Oriented Programming (OOP)?

Object Oriented Programming has emerged with the growth of the projects, starting from the break-and-manage philosophy. The main elements in the program can be abstracted and accepted as objects. Because it is abstraction, it is easier to manage, understand and develop.

For example, in an e-commerce project, the user, category and product are the most basic objects. The product has a price, name and category. In this case, the product is an object and the price, name and category are its fields.

Why Are There Design Patterns?

Design patterns are the standards that exist to realize, understand and develop the abstraction I mentioned in the previous title. It originated in roughly 1994 as a book by four authors known as the Gang of Four.

These patterns exist in order to solve certain problems more easily with standard methods known to everyone. In this way, the developer who encounters a known problem (for example, creating an object for the same object only once) will be able to quickly reach a solution by using the relevant pattern (in this case, a singleton pattern) without re-discovering America.

Ok So Why Is There SOLID?

SOLID, stands for Robert C. Martins’s top five object-oriented design patterns.

The definition of SOLID is as follows:

  • S — Single Reponsibility Principle
  • O — Open-closed Principle
  • L — Liskov Substitution Principle
  • I — Interface Segregation Principle
  • D — Dependency Inversion Principle

This article is the first leg of the series where we will take a closer look at the design patterns that correspond to each letter of SOLID, and therefore, in this article, we will examine the Single Responsibility Principle closely, Which corresponds to the letter S of SOLID!

Single Responsibility Principle

According to the single responsibility principle; There must be only one reason for a class to change, that is, there must be only one job for which a class is responsible.

For example, let’s say that when the user registers in a membership system, we will send an e-mail to the address specified by the user. In this case we will have a function to register and a function to send mail.

But big mistake! We have to look at the issue from a broader perspective. If we define a function to send mail and a function to register, we do not write any maintainable code at all. What will we do if we want to send it in SMS after sending an Mail tomorrow?

What we need to d ois to create 5 different services, namely MembershipService, LoginService, VerificationService, MailService and SmsService.

Here MembershipService is dependent on login and verification services. Verification service contains sendMail and sendSms functions and it is to run the relevant functions of the related services.

The user model that we will do the operations with:

export interface User {
firstName: string;
lastName: string;
email: string;
phone?: string;
}

The Login Service:

import { User } from "./user.model";

export interface ILoginService {
login: (user: User) => Promise<string>;
register: (user: User) => Promise<string>;
}

export class LoginService implements ILoginService {
constructor() {}

login = async (user: User) => {
return "thisIsAToken";
};

register = async (user: User) => {
return "thisIsAToken";
};
}

The Mail Service:

export interface IMailService {
sendMail: (email: string, content: string) => Promise<boolean>;
}

export class MailService implements IMailService {
constructor() {}

sendMail = async (email: string, content: string) => {
return true;
};
}

The SMS Service:

export interface ISmsService {
sendSMS: (phone: string, content: string) => Promise<boolean>;
}

export class SmsService implements ISmsService {
constructor() {}

sendSMS = async (phone: string, content: string) => {
return true;
};
}

The Verification Service:

import { IMailService } from "./MailService";
import { ISmsService } from "./SmsService";

export interface IVerificationService {
sendSms: (phone: string) => Promise<void>;
sendMail: (email: string) => Promise<void>;
}

export class VerificationService implements IVerificationService {
private mailService: IMailService;
private smsService: ISmsService;
// dependency injection
constructor(mailService: IMailService, smsService: ISmsService) {
this.mailService = mailService;
this.smsService = smsService;
}

sendMail = async (email: string): Promise<void> => {
const content = this.createContent();
const result: boolean = await this.mailService.sendMail(email, content);
};

sendSms = async (phone: string): Promise<void> => {
const content = this.createContent();
const result: boolean = await this.smsService.sendSMS(phone, content);
};

private createContent = (): string => {
return "thisIsVerificationContent";
};
}

The Membership Service:

import { ILoginService } from "./LoginService";
import { IVerificationService } from "./VerificationService";

export interface IMembershipService {
create: (
firstName: string,
lastName: string,
email: string,
phone?: string
) => Promise<string>;
}

export class MembershipService implements IMembershipService {
private loginService: ILoginService;
private verificationService: IVerificationService;

constructor(
loginService: ILoginService,
verificationService: IVerificationService
) {
this.loginService = loginService;
this.verificationService = verificationService;
}

create = async (
firstName: string,
lastName: string,
email: string,
phone?: string
) => {
const token: string = await this.loginService.login({
firstName,
lastName,
email,
phone,
});
this.verificationService.sendMail(email).then();
return token;
};
}

The application main file

import { LoginService } from "./LoginService";
import { MailService } from "./MailService";
import { SmsService } from "./SmsService";
import { VerificationService } from "./VerificationService";
import { MembershipService } from "./MembershipService";

const loginService = new LoginService();
const mailService = new MailService();
const smsService = new SmsService();

const verificationService = new VerificationService(mailService, smsService);
const membershipsService = new MembershipService(
loginService,
verificationService
);

(async () => {
const token = await membershipsService.create(
"Sami",
"Salih",
"info@ssibrahimbas.com"
);
console.log("token -> ", token);
})();

Check out how clean and maintainable the code is!

That’s it for today. Thank you for coming this far. The subject of my text article will be to take a closer look at the Open-Closed Principle, which corresponds to the second letter of SOLID! Until then take care!

Code’s used in the article

--

--