Mastering SOLID Principles in TypeScript for Better Code Design
Abstracted Payment Gateway using Inteface
interface PaymentGateway{
pay(amount:number):void
}
Single Responsiblity Principle (SRP): A class should have only one reason to change or should handle one action Handles logging of payment activities.
=> Before Violates SRP
class Payment {
pay(amount: number): void {
console.log(`LOG: Paying $${amount}`);
}
}
=> After: Follows SRP by separating logging and payment.
class PaymentLogger {
log(message: string): void {
console.log(`LOG: ${message}`);
}
}
class Payment {
constructor(private logger: PaymentLogger) {}
pay(amount: number): void {
this.logger.log(`Paying $${amount}`);
}
}
Open Closed Principle (OCP) - Open for extension, closed for modification. In this lesson, the PaymentGateway
class uses the PaymentGateway
interface. Now, any interface that implements PaymentGateway
can be used to process payments. This means that to support a new PaymentGateway
, we don't need to modify the MakePayment
class, as MakePayment
doesn't rely on a specific implementation.
class MakePayment{
private readonly paymentGateway:PaymentGateway;
constructor(paymentGateway:PaymentGateway){
this.paymentGateway=paymentGateway
}
payment(amount:number){
this.paymentGateway.pay(amount)
}
}
=> So there if we need to introduce new PaymentGateway Like Stripe Or Something We
can do that by ease
class Stripe implements PaymentGateway {
constructor(private logger: PaymentLogger) {}
pay(amount: number): void {
this.logger.log(`Paying $${amount} using Stripe`);
}
}
// Usage:
const stripePayment = new MakePayment(new Stripe(new PaymentLogger()));
stripePayment.payment(50)
LISKOV Substitution Principle - A child class can substitute for a parent class. As long as any class implements the PaymentGateway interface, it can be used as a substitute for the PaymentGateway interface.
class Paypal implements PaymentGateway{
constructor(
private readonly paymentLogger:PaymentLogger
){
}
pay(amount:number){
this.paymentLogger.log(`Paying $${amount} using Paypal`)
}
}
Interface Segregation Principle (ISP): A class should never be forced to implement an interface that it dosen't use
=> Before it violates ISP
interface WorkerInterface{
working():void
eating():void
}
class Human implements WorkerInterface{
eating(){
console.log("Eating")
}
working(){
console.log("Working")
}
}
=> In this case the Robot implements WorkerInterface and it must implement eating
method although robot dose not eat, for that we can segregate the interface
class Robot implements WorkerInterface{
eating(){
console.log("Eating")
}
working(){
console.log("Working")
}
}
interface WorkingInterface{
working():void
}
interface EatingInterface{
eating():void
}
/**
* Now we can create class and implement applicable Interface
*/
class HumanWorker implements WorkingInterface,EatingInterface{
eating(){
console.log("Eating")
}
working(){
console.log("Working")
}
}
class RobotWorker implements WorkingInterface{
working(){
console.log("Working")
}
}
Dependency Inversion Principle (DIP) - High-level modules should not depend on low-level modules. Instead, both should depend on an abstraction. Here, the MakePayment class is a high-level module, and the Paypal class is a low-level module. To follow the Dependency Inversion Principle (DIP), MakePayment shouldn't depend on Paypal. Instead, both should depend on the PaymentGateway interface or an abstract PaymentGateway.
class MakePayment{
private readonly paymentGateway:PaymentGateway;
constructor(paymentGateway:PaymentGateway){
this.paymentGateway=paymentGateway
}
payment(amount:number){
this.paymentGateway.pay(amount)
}
}
class Paypal implements PaymentGateway{
constructor(
private readonly paymentLogger:PaymentLogger
){
}
pay(amount:number){
this.paymentLogger.log(`Paying $${amount} using Paypal`)
}
}