SOLID
Every each of this words(SOLID) is one word first, like the S - Single Responsibility, O - OPEN/CLOSED, L - LISKOV SUBSTITUTION, I - INTERFACE SEGREGATION, D - Dependency Inversion.
I want explain these by typescript, Please note that if your code doesn't comply with these principles or in a dry form, you must change the rest of your code in a cascading manner when changing your code, which means rigidity.
Single Responsibility Principle
A class should have one, and only one, reason to change.
/**
S - SRP - Single Responsibility Principle
*/
class User
{
constructor(public logger : any) {
this.logger = logger
}
public UserParseJson(data : String) {
try {
return data
} catch (error) {
return this.logger.log(error)
}
}
}
class Logger
{
public log(message : String) {
return message
}
}
let logger = new Logger();
let user = new User(logger);
In the above code we have a user class that contains a constructor method as well as a UserParseJson method, which is responsible only for returning data or errors.
There is also another class called logger that contains a log method and the only function of this method is to return the message.
Finally, we instantiate logger class then we injected this variable into the User class, infact passed data to the User constructor.
Well, this is called single responsibility.
OPEN/CLOSED Principle
Entities should be open for extension, but closed for modification.
/**
O - OCP - OPEN/CLOSED Principle
*/
interface PaymentInterface
{
acceptPayment() : any;
}
class BitCoinPaymentMethod implements PaymentInterface
{
public acceptPayment() : any {
// logic to accept BitCoin
}
}
class CreditCardPaymentMethod implements PaymentInterface
{
public acceptPayment() : any {
// logic to accept CreditCard
}
}
class Payment
{
public begin(PaymentInterface : any) {
return PaymentInterface.acceptPayment();
}
}
let payment = new Payment();
seperate extensible behavior behind an interface.
In the above code, we first put an interface and inside is a method, as well as two classes that implements of this inteface.
And flip the dependencies.
In the above code we also have a Paymnet class where PaymentInterface is injected into the begin method of this class and the accept Payment method located in the previous two classes returns without understanding.
LISKOV SUBSTITUTION Principle
Drived classes must be substitutable for their base classes.
/**
L - LCP - LISKOV SUBSTITUTION Principle
*/
interface CarInterface
{
drive() : string;
}
class Car implements CarInterface
{
public drive() : string {
return "Please drive safely";
}
}
class Bmw extends Car
{
public drive() : string {
return "Please drive safely with Bmw";
}
}
const car = new Car();
const bmw = new Bmw();
In the above code, first there is an interface and the Car class uses the method of this interface called drive and returns a string.
The Bmw class then inherits from the Car class and returns a string from the drive method.
Just be aware that if the class that implements returns from the interface of a string or array or anything else, the class that inherits from this class must also use the same DataType to return the data, otherwise this principle becomes violate.
INTERFACE SEGREGATION Principle
/**
I - ISP - INTERFACE SEGREGATION Principle
*/
interface BirdInterface
{
fly() : any;
}
interface WalkInterface
{
walk() : any;
}
class Parrot implements BirdInterface, WalkInterface
{
public fly() : any {
//
}
public walk() : any {
//
}
}
class Penguin implements WalkInterface
{
public walk() : any {
//
}
}
A client should not be forced to implement an interface that it doesn't use.
As you can see in the above code there are two interfaces with each of which have two methods called fly and walk. We also have a Parrot class that implements and there are two methods mentioned inside.
There is also a class called Penguin. As we know, penguin can not fly, so it does not need to fly.
Dependency Inversion Principle
/**
D - DIP - Dependency Inversion Principle
*/
interface MailerInterface
{
send() : any;
}
class SmtpMailer implements MailerInterface
{
public send() : any {
//
}
}
class SendGridMailer implements MailerInterface
{
public send() : any {
//
}
}
class SendWelcomeMessage
{
public mailer : any
constructor(MailerInterface : any) {
this.mailer = MailerInterface
}
}
High-level modules shouldn't depend on low-level modules instead on anstractions, not on concretions.
low-level modules
First we put an interface and a method called send inside and we implement this in two classes SmtpMailer and SendGridMailer.
High-level module
There is also a class at the end called SendWelcomeMessage in which we injected the interface into the constructor method.
I hope this article has helped you write your code Dry and in the end there is no need to use these principles.