mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
feature: 如何设计一个校验器
This commit is contained in:
348
Chapter1 - iOS/1.110.md
Normal file
348
Chapter1 - iOS/1.110.md
Normal file
@@ -0,0 +1,348 @@
|
||||
## 设计一个客户端校验器
|
||||
|
||||
> 订单在提交的时候会面临不同的校验规则,不同的校验规则会有不同的处理。假设这个处理就是弹窗。
|
||||
>
|
||||
> 有的时候会命中规则1,则弹窗1,有的时候同时命中规则1、2、3,但由于存在规则的优先级,则会处理优先级最高的弹窗1。
|
||||
>
|
||||
> 老的业务背景下,弹窗优先级或者说校验规则是统一的。直接用函数翻译实现,写多个 if 问题不大。
|
||||
>
|
||||
> 但在新业务背景下,不同的条件,弹窗优先级不一致,之前的写法需要写大量的嵌套判断,代码难以维护。
|
||||
>
|
||||
> 所以问题抽象为:如何设计一个校验器
|
||||
|
||||
|
||||
|
||||
## 问题背景
|
||||
|
||||
为了清晰说明问题,假设线上的弹窗校验规则为:A -> B -> C
|
||||
|
||||
```Plain
|
||||
typedef NS_ENUM(NSUInteger, OrderSubmitReminderType) {
|
||||
OrderSubmitReminderTypeNormal = 0, // 没有命中校验规则
|
||||
OrderSubmitReminderTypeA, // 命中校验规则 A
|
||||
OrderSubmitReminderTypeB, // 命中校验规则 B
|
||||
OrderSubmitReminderTypeC, // 命中校验规则 C
|
||||
}
|
||||
```
|
||||
|
||||
老规则比较简单,不存在不同的校验规则,所以需求可以直接用代码翻译,不需要额外设计
|
||||
|
||||
```Shell
|
||||
+ (OrderSubmitReminderType)acquireOrderValidateType:(id)params {
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
```
|
||||
|
||||
假设只有2个弹窗条件:是否是 VIP 账户(isVIP)、是否是付费用户(isChargedAccount)。
|
||||
|
||||
- isVIP & isChargedAccount: A -> B -> C
|
||||
- isVIP & !isChargedAccount:B -> C-> A
|
||||
- !isVIP: C -> B -> A
|
||||
|
||||
如果直接改,代码就是一坨垃圾了
|
||||
|
||||
```Shell
|
||||
+ (OrderSubmitReminderType)acquireOrderValidateType:(id)params {
|
||||
if (isVIP) {
|
||||
if (isChargedAccount) {
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
} else {
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
} else {
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 思路
|
||||
|
||||
可能有些人会觉得,那不简单,我将不同组合条件下的弹窗抽取为3个方法,照样很简洁
|
||||
|
||||
```Shell
|
||||
+ (OrderSubmitReminderType)acquireOrderValidateTypeWhenIsVIPAndChargedAccount:(id)params {
|
||||
// A->B->C
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
|
||||
+ (OrderSubmitReminderType)acquireOrderValidateTypeWhenIsVIPAndNotChargedAccount:(id)params {
|
||||
// B -> C-> A
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
|
||||
+ (OrderSubmitReminderType)acquireOrderValidateTypeWhenIsNotVIP:(id)params {
|
||||
// C -> B-> A
|
||||
if ([OrderSubmitUtils validateC:params]) {
|
||||
return OrderSubmitReminderTypeC;
|
||||
}
|
||||
if ([OrderSubmitUtils validateB:params]) {
|
||||
return OrderSubmitReminderTypeB;
|
||||
}
|
||||
if ([OrderSubmitUtils validateA:params]) {
|
||||
return OrderSubmitReminderTypeA;
|
||||
}
|
||||
return OrderSubmitReminderTypeNormal;
|
||||
}
|
||||
```
|
||||
|
||||
其实不然,问题还是很多:
|
||||
|
||||
- 虽然抽取为不同方法,但是每个方法内部存在大量冗余代码,因为每个校验规则的代码是一样的,重复存在,只不过先后顺序不同
|
||||
- 存在隐含逻辑。 return 顺序决定了弹窗优先级的高低(这一点不够痛)
|
||||
|
||||
## 方案
|
||||
|
||||
那能不能优化呢?有2个思路:责任链设计模式、工厂设计模式
|
||||
|
||||
### 责任链设计模式
|
||||
|
||||
采用责任链设计模式。基类 `OrderSubmitBaseValidator` 声明接口,是一个抽象类:
|
||||
|
||||
- 有一个属性 `nextValidator` 用于指向下一个校验器
|
||||
- 有一个方法 `- (void)validate:(id)params;` 用于处理校验,内部默认实现是传递给下一个校验器。
|
||||
|
||||
```Shell
|
||||
//.h
|
||||
OrderSubmitBaseValidator {
|
||||
@property nextValidator;
|
||||
|
||||
- (void)validate:(id)params;
|
||||
- (BOOL)isValidate:(id)params;
|
||||
- (void)handleWhenCapture;
|
||||
}
|
||||
|
||||
|
||||
// .m
|
||||
#pragma mark - public Method
|
||||
- (BOOL)isValidate:(id)params {
|
||||
Assert(0, @"must override by subclass");
|
||||
return NO;
|
||||
}
|
||||
- (void)validate:(id)params {
|
||||
BOOL isValid = [self isValidate:params];
|
||||
if (isValid) {
|
||||
[self.nextValidator validate:params];
|
||||
} else {
|
||||
[self handleWhenCapture];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleWhenCapture {
|
||||
Assert(0, @"must override by subclass");
|
||||
}
|
||||
```
|
||||
|
||||
然后针对不同的校验规则声明不同的子类,继承自 `OrderSubmitBaseValidator`。根据A、B、C 3个校验规则,有:OrderSubmitAValidator、OrderSubmitBValidator、OrderSubmitCValidator。
|
||||
|
||||
子类去重写父类方法
|
||||
|
||||
```Shell
|
||||
OrderSubmitAValidator {
|
||||
- (BOOL)isValidate:(id)params {
|
||||
// 处理是否满足校验规则A
|
||||
}
|
||||
|
||||
- (void)handleWhenCapture {
|
||||
// 当不满足条件规则的时候的处理逻辑
|
||||
displayDialogA();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
为了设计的健壮,假设没有命中任何校验规则,需要如何处理?这个能力需要有兜底默认的行为,比如打印日志:`NSLog(@"暂无命中任何弹窗类型,参数为:%@",params);` 也可以由业务方传递
|
||||
|
||||
```Shell
|
||||
OrderSubmitDefaultValidator *defaultValidator = [OrderSubmitDefaultValidator validateWithBloock:^ {
|
||||
SafeBlock(self.deafaultHandler, params);
|
||||
if (!self.deafaultHandler) {
|
||||
NSLog(@"暂无命中任何弹窗类型,参数为:%@",params);
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
初始化多个校验规则
|
||||
|
||||
```Shell
|
||||
OrderSubmitAValidator *aValidator = [[OrderSubmitAValidator alloc] initWithParams:params];
|
||||
OrderSubmitBValidator *bValidator = [[OrderSubmitBValidator alloc] initWithParams:params];
|
||||
OrderSubmitCValidator *cValidator = [[OrderSubmitCValidator alloc] initWithParams:params];
|
||||
```
|
||||
|
||||
不同优先级的校验如何指定:
|
||||
|
||||
```Shell
|
||||
if (isVIP) {
|
||||
if (isChargedAccount) {
|
||||
aValidator.nextValidator = bValidator;
|
||||
bValidator.nextValidator = cValidator;
|
||||
} else {
|
||||
bValidator.nextValidator = cValidator;
|
||||
cValidator.nextValidator = aValidator;
|
||||
}
|
||||
} else {
|
||||
cValidator.nextValidator = bValidator;
|
||||
bValidator.nextValidator = aValidator;
|
||||
}
|
||||
```
|
||||
|
||||
但还是不够优雅,这个优先级需要用户感知。能不能做到业务方只传递参数,内部判断命中什么弹窗优先级组合。所以接口可以设计为
|
||||
|
||||
```Shell
|
||||
[OrderSubmitValidator validateWithParams:params handleWhenNotCapture:^{
|
||||
NSLog(@"暂无命中任何弹窗类型,参数为:%@",params);
|
||||
}];
|
||||
```
|
||||
|
||||
上述方法其实等价于
|
||||
|
||||
```Shell
|
||||
let validateType = [OrderSubmitValidator generateTypeWithParams:params];
|
||||
[OrderSubmitValidator validateWith:validateType];
|
||||
```
|
||||
|
||||
优点:
|
||||
|
||||
1. 解决了现在的错误弹窗的隐含逻辑,后续人接手,弹窗优先级清晰可见,提高可维护性,减少出错概率
|
||||
2. 对于判断(校验)的增减都无需关心其他的校验规则。类似维护链表,仅在一开始指定即可,符合“开闭原则
|
||||
3. 对于现有校验规则的修改足够收口,每个规则都有自己的 validator 和 validate 方法
|
||||
4. 目前弹窗优先级针对 EVA 、BTC 存在不同优先级顺序,如果按照现有的方案实施,则会存在很多冗余代码
|
||||
|
||||
### 工厂设计模式
|
||||
|
||||
设计基类
|
||||
|
||||
```Shell
|
||||
OrderSubmitBaseValidator {
|
||||
- (void)validate;
|
||||
|
||||
- (BOOL)validateA;
|
||||
- (BOOL)validateB;
|
||||
- (BOOL)validateC;
|
||||
}
|
||||
|
||||
- (void)validate {
|
||||
Assert(0, @"must override by subclass");
|
||||
}
|
||||
|
||||
- (BOOL)validateA {
|
||||
// 判断是否命中规则 A
|
||||
}
|
||||
- (BOOL)validateB {
|
||||
// 判断是否命中规则 B
|
||||
}
|
||||
|
||||
- (BOOL)validateC {
|
||||
// 判断是否命中规则 C
|
||||
}
|
||||
```
|
||||
|
||||
根据不同的弹窗优先级条件,声明3个不同的子类:`OrderSubmitAValidator`、`OrderSubmitBValidator`、`OrderSubmitCValidator`。各自重写 `validate` 方法
|
||||
|
||||
```Shell
|
||||
OrderSubmitAValidator {
|
||||
- (void)validate {
|
||||
[self validateA];
|
||||
[self validateB];
|
||||
[self validateC];
|
||||
}
|
||||
}
|
||||
|
||||
OrderSubmitBValidator {
|
||||
- (void)validate {
|
||||
[self validateB];
|
||||
[self validateC];
|
||||
[self validateA];
|
||||
}
|
||||
}
|
||||
|
||||
OrderSubmitCValidator {
|
||||
- (void)validate {
|
||||
[self validateC];
|
||||
[self validateB];
|
||||
[self validateA];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
设计工厂类`OrderSumitValidatorFactory`,提供工厂初始化方法
|
||||
|
||||
```Shell
|
||||
OrderSumitValidatorFactory {
|
||||
+ (OrderSubmitBaseValidator *)generateValidatorWithParams:(id)params;
|
||||
}
|
||||
|
||||
+ (OrderSubmitBaseValidator *)generateValidatorWithParams:(id)params {
|
||||
if (isVIP) {
|
||||
if (isChargedAccount) {
|
||||
return [[OrderSubmitAValidator alloc] initWithParams:params];
|
||||
} else {
|
||||
return [[OrderSubmitBValidator alloc] initWithParams:params];
|
||||
}
|
||||
} else {
|
||||
return [[OrderSubmitCValidator alloc] initWithParams:params];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
优点:
|
||||
|
||||
- 没有重复逻辑,判断方法都守口在基类中
|
||||
- 优先级的关系维护在不同的子类中,各司其职,独立维护
|
||||
|
||||
最后选什么?组合优于继承,个人倾向使用责任链模式去组织。
|
||||
@@ -112,4 +112,5 @@
|
||||
* [106、NSUserDefault 底层原理探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.106.md)
|
||||
* [107、IM技术](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.107.md)
|
||||
* [108、精准测试](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.108.md)
|
||||
* [109、汇编学习](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.109.md)
|
||||
* [109、汇编学习](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.109.md)
|
||||
* [110、设计一个客户端校验器](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.110.md)
|
||||
@@ -112,6 +112,7 @@
|
||||
* [107、IM技术](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.107.md)
|
||||
* [108、精准测试](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.108.md)
|
||||
* [109、汇编学习](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.109.md)
|
||||
* [110、设计一个客户端校验器](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.110.md)
|
||||
* [Chapter2 - Web FrontEnd](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/chapter2.md)
|
||||
* [1、-last-child与-last-of-type你只是会用,有研究过区别吗?](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.1.md)
|
||||
* [2、正则表达式](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.2.md)
|
||||
|
||||
Reference in New Issue
Block a user