什么是开闭原则
Software entities like classes,modules and functions should be open for extension but closed for modifications
一个软件实体, 如类, 模块, 函数等应该对扩展开放, 对修改封闭.
从需求出发
UI
基础状态
1 2 3 4 5 6 7
| 0 未进行KYC认证 1 KYC1 审核中 2 KYC1 未通过 3 KYC1 已通过 4 KYC2 审核中 5 KYC2 未通过 6 KYC2 已通过
|
补充说明:KYC1 认证之后才可以进行KYC2认证
需求说明
在KYC.1 认证的卡片内
状态 |
展示内容 |
样式 |
0 |
KYC.1 未认证 |
|
1 |
KYC.1 审核中 |
|
2 |
KYC.1 审核失败 |
|
3 |
KYC.1 已认证 |
|
4 |
KYC.1 已认证 |
|
5 |
KYC.1 已认证 |
|
6 |
KYC.1 已认证 |
|
在KYC.2 认证的卡片内
状态 |
展示内容 |
样式 |
0 |
KYC.2 未认证 |
|
1 |
KYC.2 未认证 |
|
2 |
KYC.2 未认证 |
|
3 |
KYC.2 未认证 |
|
4 |
KYC.2 审核中 |
|
5 |
KYC.2 审核失败 |
|
6 |
KYC.2 已认证 |
|
在头像的浮框内
状态 |
展示内容 |
样式 |
0 |
KYC.1 未认证 |
|
1 |
KYC.1 审核中 |
|
2 |
KYC.1 审核失败 |
|
3 |
KYC.1 已认证 |
|
4 |
KYC.2 审核中 |
|
5 |
KYC.2 审核失败 |
|
6 |
KYC.2 已认证 |
|
代码实现
第一版
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| const tagConfig = (step) => { const kyc = { 1: 'KYC.1', 2: 'KYC.2' } return { default: { text: kyc[step] + '未认证', type: 'kycUnauthorized', iconName: 'kycUnauthorized' }, unauthorized: { text: kyc[step] + '未认证', type: 'kycUnauthorized', iconName: 'kycUnauthorized' }, inReview: { text: kyc[step] + '审核中', type: 'kycInReview', iconName: 'kycInReview' }, failed: { text: kyc[step] + '审核失败', type: 'kycFailed', iconName: 'kycFailed' }, success: { text: kyc[step] + '已认证', type: 'kycSuccess', iconName: 'kycSuccess' } } }
export const KYCTag = observer((props: KYCTagProps) => { const { kycStep, className } = props const { certifiedStatus: kycStatus } = store.tradeStore.kycInfo
const [tagType, settagType] = useState(tagConfig(kycStatus).default)
useIsomorphicLayoutEffect(() => { if (kycStep === 1) { switch (kycStatus) { case -1: case 0: return settagType(tagConfig(kycStep).unauthorized) case 1: return settagType(tagConfig(kycStep).inReview) case 2: return settagType(tagConfig(kycStep).failed) case 3: case 4: case 5: case 6: return settagType(tagConfig(kycStep).success) default: return settagType(tagConfig(kycStep).default) } } if (kycStep === 2) { switch (kycStatus) { case -1: case 0: case 1: case 2: case 3: return settagType(tagConfig(kycStep).unauthorized) case 4: return settagType(tagConfig(kycStep).inReview) case 5: return settagType(tagConfig(kycStep).failed) case 6: return settagType(tagConfig(kycStep).success) default: return settagType(tagConfig(kycStep).default) } } switch (kycStatus) { case -1: case 0: return settagType(tagConfig(1).unauthorized) case 1: return settagType(tagConfig(1).inReview) case 2: return settagType(tagConfig(1).failed) case 3: return settagType(tagConfig(1).unauthorized) case 4: return settagType(tagConfig(2).inReview) case 5: return settagType(tagConfig(2).failed) case 6: return settagType(tagConfig(2).success) default: return settagType(tagConfig(2).default) } }, [kycStatus, kycStep])
return <Tag text={tagType.text} iconClass={styles.iconSize} iconName={tagType.iconName} className={cn(styles[tagType.type], className)} /> })
|
代码说明: 在这一版的代码中,我们在 KYCTag
组件内,在内部通过 store 获取当前KYC的状态,从外部接受了了一个 kycStep
字段,用来表明当前是在 【KYC1】还是【KYC2】的卡片内,如果不传,就表示是在头像浮框内,通过对 kycStep
的判断,来决定展示什么样的内容。
这样的代码确实实现了需求,但是它是违反了两个原则 开闭原则 单一职责原则。
单一职责原则我们这次先不讨论,我们看看为什么说它违反了 开闭原则。
如果说我们现在新加了需求,我们在别的地方需要使用这个组件,但是需求是
状态 |
展示内容 |
0 |
开启KYC.1 认证 |
1 |
KYC.1 审核中 |
2 |
重新开启KYC.1 认证 |
3 |
开启KYC.2 认证 |
4 |
KYC.2 审核中 |
5 |
重新开启KYC.2 认证 |
6 |
KYC 已认证 |
如果我们要使用 KYCTag
这个组件,那我们必须去深入修改 KYCTag
的实现,这完全违反了开闭原则,开闭原则的宗旨:对扩展开放,对修改封闭
,简单点说,就是当我们去实现一段新的功能时,可以在原有的基础上去新增(对扩展开放),但不能修改原有代码(对修改封闭)
如果我们不使用 KYCTag
这个组件,新建个组件,但它的样式,又和 KYCTag
有极大的相似性。这就是KYCTag这个组件在设计之初的缺陷
第二版
我们既然知道了 KYCTag
的缺陷,那我们就来优化一波
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| const tagConfig = (step) => { const kyc = { 1: 'KYC.1', 2: 'KYC.2' }; return { default: { text: kyc[step] + '未认证', type: 'kycUnauthorized', iconName: 'kycUnauthorized' }, unauthorized: { text: kyc[step] + '未认证', type: 'kycUnauthorized', iconName: 'kycUnauthorized' }, inReview: { text: kyc[step] + '审核中', type: 'kycInReview', iconName: 'kycInReview' }, failed: { text: kyc[step] + '审核失败', type: 'kycFailed', iconName: 'kycFailed' }, success: { text: kyc[step] + '已认证', type: 'kycSuccess', iconName: 'kycSuccess' } } };
export const KYCTag = observer((props: KYCTagProps) => { const { kycStep, className, tagConfigMap } = props; const { certifiedStatus: kycStatus } = store.tradeStore.kycInfo;
const typeMap = useMemo(() => { if (tagConfigMap) { return tagConfigMap; }
if (kycStep === 1) { return { 0: tagConfig(1).unauthorized, 1: tagConfig(1).inReview, 2: tagConfig(1).failed, 3: tagConfig(1).success, 4: tagConfig(1).success, 5: tagConfig(1).success, 6: tagConfig(1).success }; } if (kycStep === 2) { return { 0: tagConfig(2).unauthorized, 1: tagConfig(2).unauthorized, 2: tagConfig(2).unauthorized, 3: tagConfig(2).unauthorized, 4: tagConfig(2).inReview, 5: tagConfig(2).failed, 6: tagConfig(2).success }; } return { 0: tagConfig(1).unauthorized, 1: tagConfig(1).inReview, 2: tagConfig(1).failed, 3: tagConfig(1).success, 4: tagConfig(2).inReview, 5: tagConfig(2).failed, 6: tagConfig(2).success }; }, [kycStep, tagConfigMap]);
return ( <Tag text={typeMap[kycStatus].text} iconClass={styles.iconSize} iconName={typeMap[kycStatus].iconName} className={cn(styles[typeMap[kycStatus].type], className)} /> ); });
|
代码说明: 在这一次的优化中,我们重点在 KYCTag
内接受了一个由外部传入的 tagConfigMap
值,当 tagConfigMap
的时候,我们就根据状态从 tagConfigMap
取对应的文字、icon,当 tagConfigMap
不存在的时候,我们就根据 kycStep
判断,这样一来,当我们新增了第一版的所描述的需求时,我们也可以完美应对
以上的代码在目前已有的需求不在变更的情况下,是可以完美应对的。
但是如果有一天,产品经理说我们头像浮框内不需要展示 KYC 的状态了,那我们在 KYCTag
内就产生了冗余代码
这一段代码将永远的失去了它的用武之地,如果我们想要删除它以减少冗余代码,那么不可避免的我们又再次违反了开闭原则。
在以上的代码中,其实还存在一个问题,我们有没有必要进行 kycStep
的判断,KYCTag
目前在3个地方所使用,每个地方使用 KYCTag
的时候,KYCTag
内部都会执行这个判断,但你调用方你不知道你想展示什么内容吗,不可以直接将 【状态所对应的内容】传递进来吗,如果我们将【状态所对应的内容】从外部传入,那么我们就可以去掉这个不必要的判断
第三版
1 2 3 4 5 6 7 8 9 10 11 12 13
| export const KYCTag = observer((props: KYCTagProps) => { const { className, tagConfigMap } = props; const { certifiedStatus: kycStatus } = store.tradeStore.kycInfo;
return ( <Tag text={tagConfigMap[kycStatus].text} iconClass={styles.iconSize} iconName={tagConfigMap[kycStatus].iconName} className={cn(styles[tagConfigMap[kycStatus].type], className)} /> ); });
|
在这一次的优化中,我们去掉了 useMemo
内的判断,不同状态展示什么文案、什么icon,完全是由外部去决定,以后无论是新增了状态说明,还是修改了状态文案,我们 KYCTag
是可以做到不做任何改动的(对修改封闭),新增状态,外部添加即可(对扩展开放)
在第三版优化之后我们确实遵守了开闭原则,但是这个优化结束了吗
第四版
以下优化与开闭原则无关,仅为增加容错处理
假如说产品经理在第一版新提的需求修改了,改成了如下
状态 |
展示内容 |
0 |
【不展示KYCTag】 |
1 |
KYC.1 审核中 |
2 |
【不展示KYCTag】 |
3 |
【不展示KYCTag】 |
4 |
KYC.2 审核中 |
5 |
【不展示KYCTag】 |
6 |
KYC 已认证 |
那我们希望给 KYCTag
传入的 状态与对应内容 仅传 【1】【4】【6】,那这种情况下,会出现什么情况,当KYC状态为1 的时候,它从我们传入的的 Object 内匹配不到对应内容,那就只能报错了,基于此我们还需要增强 KYCTag 的容错处理
1 2 3 4 5 6 7 8 9 10 11 12 13
| export const KYCTag = observer((props: KYCTagProps) => { const { className, tagConfigMap } = props; const { certifiedStatus: kycStatus } = store.tradeStore.kycInfo; if(!tagConfigMap[kycStatus]) return null; return ( <Tag text={tagConfigMap[kycStatus].text} iconClass={styles.iconSize} iconName={tagConfigMap[kycStatus].iconName} className={cn(styles[tagConfigMap[kycStatus].type], className)} /> ); });
|
如此一来,我们代码的稳定性与可靠性都得到了增强
第五版
可优化点在样式,不写了,与开闭原则无关
……