A comprehensive guide to understanding and implementing SOLID principles in TypeScript, with practical examples demonstrating both violations and correct implementations.
SOLID is an acronym for five design principles that help create maintainable, scalable, and robust software:
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
src/SolidPrinciples/
├── 1_SingeResponsibity/
│ ├── SingleResponsibility.ts (Correct implementation)
│ └── SingleResponsibilityViolates.ts (Violation example)
├── 2_OpenClose/
│ ├── OpenClose.ts (Correct implementation)
│ └── OpenCloseViolates.ts (Violation example)
├── 3_LiskovSubstitution/
│ ├── liskovSubstitiution.ts (Correct implementation)
│ ├── liskovSubstitiutionViolates.ts (Violation example)
│ └── LpsRules/ (Detailed rule examples)
│ ├── singatureRule/
│ ├── propertyRule/
│ └── methodRule/
├── 4_InterfaceSegregation/
│ ├── interfaceSegregation.ts (Correct implementation)
│ └── interfaceSegregationViolates.ts (Violation example)
└── 5_DependencyInversion/
├── dependecnyInversion.ts (Correct implementation)
└── dependecnyInversionViolates.ts (Violation example)
Definition: A class should have only one reason to change - it should have only one responsibility.
Example: A ShoppingCart class should only manage cart items and calculations. Printing invoices and saving to database should be handled by separate classes (ShoppingCartPrinter and ShoppingDBService).
- Correct Implementation: SingleResponsibility.ts
- Violation Example: SingleResponsibilityViolates.ts
Benefits:
- Easier to maintain and test
- Changes to one responsibility don't affect others
- Better code organization
Definition: Software entities should be open for extension but closed for modification.
Example: When adding new database types (MongoDB, PostgreSQL, DynamoDB), you should extend functionality through new classes implementing a common interface, rather than modifying existing code.
- Correct Implementation: OpenClose.ts
- Violation Example: OpenCloseViolates.ts
Benefits:
- Reduces risk of breaking existing functionality
- Makes code more flexible and extensible
- Promotes use of abstractions
Definition: Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
Example: A FixedDeposit account that throws an error when withdrawal() is called violates LSP. Instead, create separate hierarchies: NonWithdrawableAccount (for fixed deposits) and WithdrawableAccount (for savings/current accounts).
- Correct Implementation: liskovSubstitiution.ts
- Violation Example: liskovSubstitiutionViolates.ts
- Detailed Rules: LpsRules/
- Signature rules (return types, arguments, exceptions)
- Property rules (invariance, history constraints)
- Method rules (preconditions, postconditions)
Benefits:
- Ensures predictable behavior
- Enables proper polymorphism
- Prevents unexpected runtime errors
Definition: Clients should not be forced to depend on interfaces they do not use.
Example: Instead of forcing all shapes to implement both area() and volume() methods, create separate interfaces: Shape2D (only area()) and Shape3D (both area() and volume()).
- Correct Implementation: interfaceSegregation.ts
- Violation Example: interfaceSegregationViolates.ts
Benefits:
- Prevents bloated interfaces
- Reduces unnecessary dependencies
- Makes code more flexible
Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.
Example: A UserService should depend on a DBService abstraction (interface), not directly on PostgresDatabase or DynamoDB. This allows easy database swapping through dependency injection.
- Correct Implementation: dependecnyInversion.ts
- Violation Example: dependecnyInversionViolates.ts
Benefits:
- Loose coupling between modules
- Easy to test with mocks
- Flexible to change implementations
- Node.js (v14 or higher)
- npm or yarn
- Clone the repository:
git clone https://github.com/yourusername/SOLID-Principles.git
cd SOLID-Principles- Install dependencies:
npm installEach file can be run independently to see the examples in action:
# Run TypeScript files directly with ts-node (if installed)
npx ts-node src/SolidPrinciples/1_SingeResponsibity/SingleResponsibility.ts
# Or compile first, then run
tsc src/SolidPrinciples/1_SingeResponsibity/SingleResponsibility.ts
node src/SolidPrinciples/1_SingeResponsibity/SingleResponsibility.js- SRP: One class, one responsibility
- OCP: Extend, don't modify
- LSP: Subtypes must be substitutable for their base types
- ISP: Small, focused interfaces
- DIP: Depend on abstractions, not concretions