设计一个客户端校验器

订单在提交的时候会面临不同的校验规则,不同的校验规则会有不同的处理。假设这个处理就是弹窗。

有的时候会命中规则1,则弹窗1,有的时候同时命中规则1、2、3,但由于存在规则的优先级,则会处理优先级最高的弹窗1。

老的业务背景下,弹窗优先级或者说校验规则是统一的。直接用函数翻译实现,写多个 if 问题不大。

但在新业务背景下,不同的条件,弹窗优先级不一致,之前的写法需要写大量的嵌套判断,代码难以维护。

所以问题抽象为:如何设计一个校验器

问题背景

为了清晰说明问题,假设线上的弹窗校验规则为:A -> B -> C

objectivec 复制代码
typedef NS_ENUM(NSUInteger, OrderSubmitReminderType) {
  OrderSubmitReminderTypeNormal = 0,   // 没有命中校验规则
  OrderSubmitReminderTypeA,             // 命中校验规则 A
  OrderSubmitReminderTypeB,             // 命中校验规则 B
  OrderSubmitReminderTypeC,             // 命中校验规则 C
}

老规则比较简单,不存在不同的校验规则,所以需求可以直接用代码翻译,不需要额外设计

csharp 复制代码
+ (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

如果直接改,代码就是一坨垃圾了

kotlin 复制代码
+ (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个方法,照样很简洁

kotlin 复制代码
+ (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; 用于处理校验,内部默认实现是传递给下一个校验器。
objectivec 复制代码
//.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。

子类去重写父类方法

scss 复制代码
OrderSubmitAValidator {
   - (BOOL)isValidate:(id)params {
      // 处理是否满足校验规则A
  }
   
   - (void)handleWhenCapture {
      // 当不满足条件规则的时候的处理逻辑
      displayDialogA();
  }
}

为了设计的健壮,假设没有命中任何校验规则,需要如何处理?这个能力需要有兜底默认的行为,比如打印日志:NSLog(@"暂无命中任何弹窗类型,参数为:%@",params); 也可以由业务方传递

ini 复制代码
OrderSubmitDefaultValidator *defaultValidator = [OrderSubmitDefaultValidator validateWithBloock:^ {
  SafeBlock(self.deafaultHandler, params);
   if (!self.deafaultHandler) {
      NSLog(@"暂无命中任何弹窗类型,参数为:%@",params);
  }
}];

初始化多个校验规则

ini 复制代码
OrderSubmitAValidator *aValidator = [[OrderSubmitAValidator alloc] initWithParams:params];
OrderSubmitBValidator *bValidator = [[OrderSubmitBValidator alloc] initWithParams:params];
OrderSubmitCValidator *cValidator = [[OrderSubmitCValidator alloc] initWithParams:params];

不同优先级的校验如何指定:

ini 复制代码
if (isVIP) {
   if (isChargedAccount) {
      aValidator.nextValidator = bValidator;
      bValidator.nextValidator = cValidator;
  } else {
      bValidator.nextValidator = cValidator;
      cValidator.nextValidator = aValidator;
  }
} else {
  cValidator.nextValidator = bValidator;
  bValidator.nextValidator = aValidator;
}

但还是不够优雅,这个优先级需要用户感知。能不能做到业务方只传递参数,内部判断命中什么弹窗优先级组合。所以接口可以设计为

csharp 复制代码
[OrderSubmitValidator validateWithParams:params handleWhenNotCapture:^{
  NSLog(@"暂无命中任何弹窗类型,参数为:%@",params);
}];

上述方法其实等价于

ini 复制代码
let validateType = [OrderSubmitValidator generateTypeWithParams:params];
[OrderSubmitValidator validateWith:validateType];

优点:

  1. 解决了现在的错误弹窗的隐含逻辑,后续人接手,弹窗优先级清晰可见,提高可维护性,减少出错概率
  2. 对于判断(校验)的增减都无需关心其他的校验规则。类似维护链表,仅在一开始指定即可,符合"开闭原则
  3. 对于现有校验规则的修改足够收口,每个规则都有自己的 validator 和 validate 方法
  4. 目前弹窗优先级针对 EVA 、BTC 存在不同优先级顺序,如果按照现有的方案实施,则会存在很多冗余代码

工厂设计模式

设计基类

scss 复制代码
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个不同的子类:OrderSubmitAValidatorOrderSubmitBValidatorOrderSubmitCValidator。各自重写 validate 方法

ini 复制代码
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,提供工厂初始化方法

csharp 复制代码
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];
  }
}

优点:

  • 没有重复逻辑,判断方法都守口在基类中
  • 优先级的关系维护在不同的子类中,各司其职,独立维护

最后选什么?组合优于继承,个人倾向使用责任链模式去组织。

相关推荐
mit6.8247 小时前
[Docker#4] 镜像仓库 | 部分常用命令
linux·运维·docker·容器·架构
瞎姬霸爱.10 小时前
设计模式-七个基本原则之一-里氏替换原则
java·设计模式·里氏替换原则
monkey_meng10 小时前
【Rust设计模式之建造者模式】
后端·设计模式·rust·建造者模式
乌恩大侠11 小时前
了解 Open RAN 架构中的 DU 和 CU
架构
贵州晓智信息科技11 小时前
深入理解 React 架构从概览到核心机制
前端·react.js·架构
W Y11 小时前
【架构论文-1】面向服务架构(SOA)
架构·架构设计
Hello.Reader12 小时前
解析Eureka的架构
云原生·eureka·架构
️ 邪神12 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件
flutter·ios·鸿蒙·reactnative·anroid
️ 邪神13 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本Text显示
flutter·ios·鸿蒙·reactnative·anroid
航火火14 小时前
回首遥望-C++内存对齐的思考
c++·面试·架构