1. 1. 什么是开闭原则
  2. 2. 从需求出发
    1. 2.1. UI
    2. 2.2. 基础状态
    3. 2.3. 需求说明
  3. 3. 代码实现
    1. 3.1. 第一版
    2. 3.2. 第二版
    3. 3.3. 第三版
    4. 3.4. 第四版
    5. 3.5. 第五版

什么是开闭原则

Software entities like classes,modules and functions should be open for extension but closed for modifications

一个软件实体, 如类, 模块, 函数等应该对扩展开放, 对修改封闭.

从需求出发

UI

image-20221103100441821

基础状态

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 未认证 image-20221103102403790
1 KYC.1 审核中 image-20221103102504377
2 KYC.1 审核失败 image-20221103102613403
3 KYC.1 已认证 image-20221103102708111
4 KYC.1 已认证 image-20221103102708111
5 KYC.1 已认证 image-20221103102708111
6 KYC.1 已认证 image-20221103102708111

在KYC.2 认证的卡片内

状态 展示内容 样式
0 KYC.2 未认证 image-20221103103121250
1 KYC.2 未认证 image-20221103103121250
2 KYC.2 未认证 image-20221103103121250
3 KYC.2 未认证 image-20221103103121250
4 KYC.2 审核中 image-20221103103158548
5 KYC.2 审核失败 image-20221103103252947
6 KYC.2 已认证 image-20221103103318740

在头像的浮框内

状态 展示内容 样式
0 KYC.1 未认证 image-20221103102403790
1 KYC.1 审核中 image-20221103102504377
2 KYC.1 审核失败 image-20221103102613403
3 KYC.1 已认证 image-20221103102708111
4 KYC.2 审核中 image-20221103103158548
5 KYC.2 审核失败 image-20221103103252947
6 KYC.2 已认证 image-20221103103318740

代码实现

第一版

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 内就产生了冗余代码

image-20221103112016154

这一段代码将永远的失去了它的用武之地,如果我们想要删除它以减少冗余代码,那么不可避免的我们又再次违反了开闭原则。

在以上的代码中,其实还存在一个问题,我们有没有必要进行 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)}
/>
);
});

如此一来,我们代码的稳定性与可靠性都得到了增强

第五版

可优化点在样式,不写了,与开闭原则无关

……