设计一个客户端校验器

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

有的时候会命中规则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];
  }
}

优点:

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

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

相关推荐
Lee川10 小时前
深度拆解:基于面向对象思维的“就地编辑”组件全模块解析
javascript·架构
勤劳打代码10 小时前
Flutter 架构日记 — 状态管理
flutter·架构·前端框架
子兮曰16 小时前
后端字段又改了?我撸了一个 BFF 数据适配器,从此再也不怕接口“屎山”!
前端·javascript·架构
卓卓不是桌桌18 小时前
如何优雅地处理 iframe 跨域通信?这是我的开源方案
javascript·架构
Qlly18 小时前
DDD 架构为什么适合 MCP Server 开发?
人工智能·后端·架构
wvy18 小时前
iOS 26手势返回到根页面时TabBar的动效问题
ios
RickeyBoy20 小时前
iOS 图片取色完全指南:从像素格式到工程实践
ios
aiopencode1 天前
使用 Ipa Guard 命令行版本将 IPA 混淆接入自动化流程
后端·ios
二流小码农2 天前
鸿蒙开发:路由组件升级,支持页面一键创建
android·ios·harmonyos
用户881586910912 天前
AI Agent 协作系统架构设计与实践
架构