SOLID Principles in TypeScript: Advanced Level with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

SOLID Principles in TypeScript: Advanced Theory and Practice



SOLID is a set of five fundamental principles of object-oriented design that allow you to create flexible, maintainable, and scalable code. In TypeScript, thanks to strong typing, interfaces, and abstract classes, implementing these principles becomes especially effective. In this article, we will break down each principle at an advanced level with real-world TypeScript examples.



1. Single Responsibility Principle (SRP)



Theory: Each class should have only one reason to change. In TypeScript, this means a module is responsible for exactly one functionality.



Advanced Practice: Violation of SRP often manifests in "God Objects." Use composition and delegation.



// ❌ SRP violation: the class handles data, validation, and persistenceclass UserService {  constructor(private user: User) {}

validateEmail(email: string): boolean { return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email); }

saveToDatabase(): void { // saving logic }

sendWelcomeEmail(): void { // sending logic }}

// ✅ SRP: separate responsibilitiesinterface IEmailValidator { isValid(email: string): boolean;}

interface IUserRepository { save(user: User): Promise<void>;}

interface INotificationService { sendWelcome(user: User): Promise<void>;}

class UserRegistrationService { constructor( private validator: IEmailValidator, private repository: IUserRepository, private notifier: INotificationService ) {}

async register(user: User): Promise<void> { if (!this.validator.isValid(user.email)) { throw new Error('Invalid email'); } await this.repository.save(user); await this.notifier.sendWelcome(user); }}


2. Open/Closed Principle (OCP)



Theory: Classes should be open for extension but closed for modification. In TypeScript, this is achieved through abstract classes and interfaces.



Advanced Practice: Use the Strategy pattern to implement OCP.



// ❌ OCP violation: adding a new report type requires modifying the classclass ReportGenerator {  generate(data: any[], type: string): string {    if (type === 'csv') {      return data.map(row => row.join(',')).join('');    } else if (type === 'json') {      return JSON.stringify(data);    }    // adding 'xml' — we modify this method    throw new Error('Unsupported format');  }}

// ✅ OCP: use polymorphisminterface IReportFormatter { format(data: any[]): string;}

class CsvFormatter implements IReportFormatter { format(data: any[]): string { return data.map(row => row.join(',')).join(''); }}

class JsonFormatter implements IReportFormatter { format(data: any[]): string { return JSON.stringify(data); }}

class XmlFormatter implements IReportFormatter { format(data: any[]): string { // XML implementation return '<data>...</data>'; }}

class ReportGeneratorOCP { constructor(private formatter: IReportFormatter) {}

generate(data: any[]): string { return this.formatter.format(data); }}

// Usage: extend without modificationconst csvReport = new ReportGeneratorOCP(new CsvFormatter());const jsonReport = new ReportGeneratorOCP(new JsonFormatter());


3. Liskov Substitution Principle (LSP)



Theory: Objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. In TypeScript, this is especially critical due to nominal typing.

Blogs

Book Recommendations