Home >Web Front-end >JS Tutorial >Understanding SOLID design principles with easy coding examples
This article provides a clear and concise overview of the SOLID design principles, accompanied by straightforward code examples to help you grasp each concept with ease.
SOLID is a set of five design principles intended to make software designs more understandable, flexible, and maintainable.
The principles are particularly useful in object-oriented design and are commonly applied in front-end and back-end development. Here’s a brief overview of each SOLID principle with a code example in TypeScript:
A class should have one and only one reason to change, meaning it should have only one job or responsibility.
This principle encourages a focused approach, ensuring that changes or updates to one aspect of your UI won’t inadvertently affect unrelated parts.
// UserProfile.tsx import React from 'react'; interface UserProfileProps { username: string; email: string; } const UserProfile: React.FC<UserProfileProps> = ({ username, email }) => { return ( <div> <h2>{username}</h2> <p>{email}</p> </div> ); }; export default UserProfile;
Here, UserProfile is responsible only for displaying user information.
Software entities should be open for extension but closed for modification.
This approach ensures that the core components remain stable and unchanged, reducing the risk of unintended side effects when adding new functionalities.
// Alert.tsx import React from 'react'; interface AlertProps { message: string; } const Alert: React.FC<AlertProps> = ({ message }) => { return <div className="alert">{message}</div>; }; export default Alert; // SuccessAlert.tsx import React from 'react'; import Alert from './Alert'; const SuccessAlert: React.FC<{ message: string }> = ({ message }) => { return <Alert message={`Success: ${message}`} />; }; export default SuccessAlert;
Alert can be extended by SuccessAlert without modifying the original Alert component.
Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.
In simpler terms, if you have a base component or module, any derived components should be usable in place of the base component without causing unexpected issues.
// BaseButton.tsx import React from 'react'; interface BaseButtonProps { onClick: () => void; label: string; } const BaseButton: React.FC<BaseButtonProps> = ({ onClick, label }) => { return <button onClick={onClick}>{label}</button>; }; export default BaseButton; // IconButton.tsx import React from 'react'; import BaseButton from './BaseButton'; interface IconButtonProps extends BaseButtonProps { icon: string; } const IconButton: React.FC<IconButtonProps> = ({ onClick, label, icon }) => { return ( <BaseButton onClick={onClick} label={<span><i className={icon}></i> {label}</span>} /> ); }; export default IconButton;
IconButton can be used anywhere BaseButton without affecting the correctness of the application.
No client should be forced to depend on methods it does not use. This means creating specific interfaces for specific needs.
In other words, rather than creating a single large interface, break it down into smaller, focused interfaces that are tailored to individual components.
// interfaces.ts export interface Flyable { fly(): void; } export interface Swimmable { swim(): void; } // Bird.ts import { Flyable } from './interfaces'; class Bird implements Flyable { fly() { console.log('Bird is flying'); } } // Fish.ts import { Swimmable } from './interfaces'; class Fish implements Swimmable { swim() { console.log('Fish is swimming'); } }
Separate interfaces Flyable and Swimmable are created to ensure that classes only implement what they need.
High-level modules should not depend on low-level modules but on abstractions. Both should depend on abstractions.
In simpler terms, instead of components directly depending on each other, they rely on interfaces or abstract classes, making the code more adaptable to changes.
// Logger.ts export interface Logger { log(message: string): void; } export class ConsoleLogger implements Logger { log(message: string) { console.log(message); } } // UserService.ts import { Logger } from './Logger'; class UserService { constructor(private logger: Logger) {} createUser(username: string) { this.logger.log(`User created: ${username}`); } } // App.ts import { UserService } from './UserService'; import { ConsoleLogger } from './Logger'; const logger = new ConsoleLogger(); const userService = new UserService(logger); userService.createUser('JohnDoe');
Here, UserService depends on the Logger abstraction, making it flexible to change the logging mechanism without altering the UserService.
These SOLID principles help in creating software that is easy to maintain, extend, and refactor, which is essential for developing robust front-end and back-end applications.
The above is the detailed content of Understanding SOLID design principles with easy coding examples. For more information, please follow other related articles on the PHP Chinese website!