# 什么是 SOLID 原则
其是面向对象设计的五大基本原则,为了创建维护性高、扩展性高的代码。起源是 Java、C++ 这类强面向对象对语言,但在前端、JS 中也能有很好的借鉴作用。
# 单一职责原则 (Single Responsibility Principle, SRP)
每个类 / 模块应该只有单一的职责,降低类之间的耦合度,使代码可维护性高。
例如 react 组件有时会同时负责 UI、业务逻辑。
1 2 3 4 5 6 7
| const UserProfile = (userId) => { const [username, setUsername] = useState(); useEffect(() => { request(userid).then(data => setUsername(data.username)); }) return <div>{username}</div>; }
|
我们其实可以将业务逻辑抽离为 hooks,使组件只负责 UI 渲染。
1 2 3 4 5 6 7 8 9 10 11 12
| const useUserData = (userId) => { const [username, setUsername] = useState(); useEffect(() => { request(userid).then(data => setUsername(data.username)); }) return username; }
const UserProfile = (userId) => { const username = useUserData(userId); return <div>{username}</div>; }
|
# 开放 / 封闭原则(OCP)
函数应该对扩展开放,对修改封闭。避免主体函数被修改可能的风险。
例如有一个验证规则函数,有时新增规则需要在主体函数内新增判断分支。
1 2 3 4 5 6 7 8 9
| const verifyEmail = (email) => { if (email.length < 5) { return false; } else if (!email.includes('@')) { return false; }
return true; }
|
我们可以把规则集作为可扩充的数组或函数参数列表传入,而不修改原来的主体函数。
1 2 3 4 5 6 7 8
| const verifyEmail = (email, rules) => { return rules.every(rule => rule(email)); }
const lengthRule = input => input.length >= 5; const emailRule = input => input.includes('@');
verifyEmail('imtangx@gmail.com', [lengthRule, emailRule]);
|
# 里氏替换原则(LSP)
子类应该能够完全替代父类,确保在原来使用父类的地方使用子类,代码的正确性不受到影响。
例如有一个子类的逻辑函数与父类是相悖的,那就不应该成为它的子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Bird { fly() { console.log('我是鸟所以我会飞'); } }
class Sparrow extends Bird { fly() { console.log("我是麻雀所以我会飞"); } }
class Penguin extends Bird { fly() { console.log("我是企鹅我不会飞只会游泳"); } }
|
一种做法是把 Fly 行为分离出去作为不同的 Bird 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class FlyingBird { fly() { } }
class SparrowLSP extends FlyingBird { fly() { console.log("我是麻雀所以我会飞"); } }
class SwimmingBird { swim() { } }
class PenguinLSP extends SwimmingBird { swim() { console.log("我是企鹅所以我会游泳"); } }
|
# 接口隔离原则(ISP)
应该减少一个类依赖它不需要使用的方法 / 参数,应该将接口分解以实现更精确的依赖关系。
例如在 react 组件中有时会传入它不需要的 props,导致耦合度高。
1 2 3 4 5 6 7 8 9
| function MultiPurposeComponent({ user, posts, comments }) { return ( <div> <UserProfile user={user} /> <UserPosts posts={posts} /> <UserComments comments={comments} /> </div> ); }
|
可以适当的把组件进行拆分,使其只依赖自己的数据。
1 2 3 4 5 6 7 8 9 10 11
| function UserProfileComponent({ user }) { return <UserProfile user={user} />; }
function UserPostsComponent({ posts }) { return <UserPosts posts={posts} />; }
function UserCommentsComponent({ comments }) { return <UserComments comments={comments} />; }
|
# 依赖倒置原则(DIP)
高级模块不应该依赖于低级模块。两者都应该依赖于抽象(例如接口)。
例如一个 UserComponent 组件依赖于 fetchUser 的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function fetchUser(userId) { return fetch(`/api/users/${userId}`).then(res => res.json()); }
function UserComponent({ userId }) { const [user, setUser] = useState(null);
useEffect(() => { fetchUser(userId).then(setUser); }, [userId]);
return <div>{user?.name}</div>; }
|
应该把数据请求放到一个抽象接口中,使所有实现了 fetchUser 的类都能被 userComponment 所用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| interface UserDataService { getUser(userId: string): Promise<any>; }
class FetchUserDataService implements UserDataService { async getUser(userId: string): Promise<any> { const res = await fetch(`/api/users/${userId}`); return res.json(); } }
class LocalCacheUserDataService implements UserDataService { private cache: Record<string, any> = {};
async getUser(userId: string): Promise<any> { if (this.cache[userId]) { return Promise.resolve(this.cache[userId]); } const userData = { id: userId, name: `Cached User ${userId}` }; this.cache[userId] = userData; return Promise.resolve(userData); } }
interface UserComponentProps { userId: string; userDataService: UserDataService; }
function UserComponent({ userId, userDataService }: UserComponentProps) { const [user, setUser] = useState(null);
useEffect(() => { userDataService.getUser(userId).then(setUser); }, [userId, userDataService]);
return <div>{user?.name}</div>; }
<UserComponent userId="1" userDataService={apiService} /> <UserComponent userId="2" userDataService={cacheService} />
|
参考文章:
🚀🚀🚀 (在 JavaScript 和 TypeScript 框架中应用 SOLID 原则