Education
Last updated on Aug 20, 2024
Last updated on Aug 26, 2024
In the ever-evolving world of JavaScript, writing clean, maintainable, and scalable code is more crucial than ever. While frameworks and libraries provide valuable tools, mastering the fundamentals of software design is equally important. This is where the SOLID principles come into play.
Envisioned by Robert C. Martin (Uncle Bob), the SOLID principles are five guidelines for crafting robust and agile object-oriented code. While primarily targeted at object-oriented languages, these principles also translate beautifully to JavaScript's flexible nature.
Let's dive into each principle and explore how we can apply them in our JavaScript code:
Principle: Every class or module should have a single, well-defined responsibility.
Benefits: Improves code focus, simplifies debugging, and enhances understanding.
Implementation:
Example: Instead of a single UserManager class handling user authentication, data validation, and profile management, implement separate classes for each responsibility, making the code clearer and easier to maintain.
Before (Violates SRP):
1class UserManager { 2 constructor(authService, db) { 3 this.authService = authService; 4 this.db = db; 5 } 6 7 authenticate(username, password) { 8 // Authentication logic using authService 9 } 10 11 validateUserData(data) { 12 // Data validation logic 13 } 14 15 createUserProfile(data) { 16 // Profile creation logic using db 17 } 18 19 getUserProfile(userId) { 20 // Profile retrieval logic using db 21 } 22} 23
After (SRP Applied):
1class AuthenticationService { 2 authenticate(username, password) { 3 // Authentication logic 4 } 5} 6 7class UserDataValidator { 8 validate(data) { 9 // Data validation logic 10 } 11} 12 13class UserDatabase { 14 createUserProfile(data) { 15 // Profile creation logic 16 } 17 18 getUserProfile(userId) { 19 // Profile retrieval logic 20 } 21} 22
Principle: Software entities (classes, modules) should be open for extension but closed for modification.
Benefits: Facilitates future enhancements without rewriting existing code.
Implementation:
Example: Create an AbstractShape interface with methods for calculating area and perimeter. Concrete shapes like Circle and Square can implement this interface without modifying the original code.
Before (Violates OCP):
1function calculateArea(shape) { 2 if (shape.type === "circle") { 3 return Math.PI * shape.radius * shape.radius; 4 } else if (shape.type === "square") { 5 return shape.side * shape.side; 6 } else { 7 throw new Error("Invalid shape type"); 8 } 9}
After (OCP Applied):
1interface Shape { 2 calculateArea(): number; 3} 4 5class 6 7Circle 8 9implements 10 11Shape 12 13{ 14 radius: number; 15 16 constructor(radius: number) { 17 this.radius = radius; 18 } 19 20 calculateArea(): number { 21 return 22 23Math.PI * this.radius * this.radius; 24 } 25} 26 27class Square implements Shape { 28 side: number; 29 30 constructor(side: number) { 31 this.side = side; 32 } 33 34 calculateArea(): number { 35 return this.side * this.side; 36 } 37} 38 39function calculateArea(shape: Shape) { 40 return shape.calculateArea(); 41} 42
Principle: Subtypes should be substitutable for their base types without altering the program's correctness.
Benefits: Ensures consistency and predictability when replacing objects.
Implementation:
Example: If a function expects a Shape object to calculate its area, any valid subtype like Circle or Square should seamlessly replace it, maintaining the expected behavior.
Example (LSP Applied):
1interface Shape { 2 calculateArea(): number; 3} 4 5class 6 7Rectangle 8 9implements 10 11Shape 12 13{ 14 width: number; 15 height: number; 16 17 constructor(width: number, height: number) { 18 this.width = width; 19 this.height = height; 20 } 21 22 calculateArea(): number { 23 return 24 25this.width * this.height; 26 } 27} 28 29// Correctly substitutable (LSP adhered to) 30function drawShape(shape: Shape) { 31 const area = shape.calculateArea(); 32 // Draw the shape based on its area 33} 34 35drawShape(new Rectangle(5, 4)); // Valid 36
Principle: Clients should not be forced to depend on interfaces they don't use.
Benefits: Reduces coupling and improves modularity.
Implementation:
Example: Instead of a single UserInterface with methods for both admin and user features, create separate interfaces (AdminInterface and UserInterface) exposing only relevant methods for each type of user.
Before (Violates ISP):
1interface UserInterface { 2 login(): void; 3 logout(): void; 4 changePassword(): void; 5 createPost(): void; 6 deletePost(): void; 7}
After (ISP Applied):
1interface AuthenticationInterface { 2 login(): void; 3 logout(): void; 4 changePassword(): void; 5} 6 7interface PostManagementInterface { 8 createPost(): void; 9 deletePost(): void; 10} 11
Principle: Depend on abstractions, not concretions. High-level modules should not depend on low-level modules, both should depend on abstractions.
Benefits: Promotes loose coupling and enables flexible dependency injection.
Implementation:
Example: Instead of directly referencing a specific data storage implementation in your application logic, rely on an abstract DataStore interface. This allows injecting different implementations (e.g., local storage, API clients) at runtime based on context.
Before (Violates DIP):
1class UserService { 2 constructor(private localStorage: LocalStorage) {} 3 4 saveUser(user: User) { 5 this.localStorage.setItem("user", JSON.stringify(user)); 6 } 7}
After (DIP Applied):
1interface DataStore { 2 setItem(key: string, value: string): void; 3} 4 5class UserService { 6 constructor(private dataStore: DataStore
Applying these principles might require an initial investment in refactoring and adjusting your coding mindset. However, the long-term benefits are substantial:
By embracing SOLID principles in your JavaScript development, you'll be on your way to crafting robust, adaptable, and future-proof applications. Remember, the journey of mastering SOLID is continuous, but the rewards are well worth the effort!
This guide provides a valuable starting point for your journey toward incorporating SOLID principles into your JavaScript development.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.