换你会怎么实现?一次巧用工厂模式进行的优化(故事篇)

大家好,我是祯民。很长时间没有更文了,这段期间一直在忙业务和 AIGC 技术建设上的事情,遇到一个很值得和大家分享的改造,内容不复杂但很有借鉴意义,为便于大家理解,全文会尽量以讲故事和类比的形式来介绍。废话不多说我们直接进入正题。

背景 - AIGC 的法务要求

事情的起因是这样的,最近 AIGC 对各行各业都产生了一些影响,很多团队都在尝试将业务与大模型融合,我也在负责一些和这个相关的技术建设,比如说 AI 自动生成单元测试,AI 辅助搭建 eslint plugin 之类的,载体都是以 vscode 插件的形式。

因为模型本身服务并不是部署在中国,所以出于对代码安全的保护,法务同学对我的技术建设就有了一些要求和限制,比如仓库密级要达到要求,敏感词过滤,人员加白等等,经过这些加白确认通过的仓库和同学才能正常使用插件。

为了能支持法务同学的这个需求,所以之前我定义了一个加白类,简单来说可以理解成下面的伪代码

typescript 复制代码
class Limit {
   // ... 一些变量
    
   async validateAvailable() {
     // ... 校验入口
   }
   // ... 其他的一些不会直接调用的 private 函数
}

这个 Limit 类会向外暴露一个异步的 validateAvailable 函数,然后在需要使用的场景,我们需要调用它并且得到通过后才能进行下一步的操作,类似下面的调用

typescript 复制代码
const generateUnitTest = vscode.commands.registerCommand(
    'chatgpt---ui-unit-test.generate-unit-test',
    uri => {
      const filePath = `/${uri.path.substring(1)}`;
      const limit = new Limit();
      limit.validateAvailable().then(() => {
          // 做你本来要做的事情
      });
    }
  );

这个过程用图来表示就是下面的样子

导火索 - 多个应用方需要接入 & 政令可能频繁产生 breaking change

这个实现其实没啥问题,因为之前我只有一个功能类,所以包一层就实现了,很方便代码看上去也还行。但是随着功能的增加,比如后续我可能还要支持单元测试的校验,解释等功能,这些功能也需要加白,那么就都需要进行上面的那一步操作。

除繁琐外,这个流程其实还存在一个很大的问题,因为插件类还是插件的直接下游,这个关系就像政府机构(法务)下发相关政令要求到公司(插件),公司(插件)为落实政策,开发了一个 sdk 要求每个个体(插件类)按规定接入,看上去是不是还 ok?

但是实际上,这个公司(插件)并没有一个传达机制,当这个政令(法务)改变的时候,有可能某天 limit 类进行了 breaking change,不再暴露这个函数,或者需要更多的函数来辅助限制时,插件需要对每个引用了这个类的逻辑都进行统一的修改,改造起来其实是很低效的,而且容易遗漏。

应用方(插件类)表示原来我才是小丑,扔了一个 sdk 就甩手掌柜了~

担起责任 - 提供工厂来加工产品

在这个场景下,比起这样的实现我们更适合使用工厂模式,因为所有的插件类都需要接入加白逻辑,加白逻辑就像是一个工厂,产品进入工厂后,才能真正落地销售。

相比 sdk 的模式,工厂模式有几个好处:

  • 调用上更加简洁明了,应用方不需要关注我应该调用加白类中的哪个函数,做什么样的操作,他需要的只有将源码类投入工厂中,然后使用工厂加工出来的新类即可。
  • 后续如果法务政令变了,不需要应用方来接入改造,只需要工厂本身调整流程,在生产产品的过程中影响流水线即可

到这里大家应该已经理解工厂模式的种种好处了,我们来看看怎么对进行改造

typescript 复制代码
export default class ValidateFactory {
  private productClass;

  constructor(productClass: any) {
    this.productClass = productClass;
  }

  async create(...args: any[]>): Promise<any[]> {
    let productObj: any = null;
    const available = await this.validateAvailable();
    if (available) {
      productObj = new this.productClass(...args);
    }
    return productObj;
  }
  
  // ... 其他校验逻辑和函数
}

在上面的例子中,我们提供了一个 create 函数,这里面我们会完成所有的加白逻辑,如果校验通过,我们将根据外部透传进来的类来创建一个对象进行返回,这样就能做到所有的加白逻辑都收敛到加白类中了。

调用的话也非常简单,如下例子

typescript 复制代码
const generateUnitTest = vscode.commands.registerCommand(
    'chatgpt---ui-unit-test.generate-unit-test',
    uri => {
      const filePath = `/${uri.path.substring(1)}`;
      const generateTestFactory = new ValidateFactory<typeof GenerateUnitTest>(GenerateUnitTest);
      generateTestFactory.create().then((generateTestProduct) => {
        if (generateTestProduct) {
          generateTestProduct.generateUnitTest(filePath);
        }
      });
    }
  );

但到这里还没有完,因为我们的类型都没有定义,所以得到的 generateTestProduct 是没有类型提示的,我们不知道工厂吐出来给我们的是啥,也不知道该怎么用它,除非我们深入工厂内部,了解工厂到底是怎么做的。

说白了,我们他喵的买了三无产品

类型编程 - 坚决抵制三无产品

对类型编程还不熟悉的同学可以看看 《一文吃透 TypeScript 类型编程(含精选类型体操编程题)》

三无产品那是绝对不能忍的,万一哪一天生产的是清洁剂,给当肥宅快乐水喝了那不是完了~我们来梳理一下这个工厂函数中类型的关系:

  • productClass 是一个类
  • create 的入参需要匹配 productClass 的构造函数入参类型
  • create 的返回需要匹配 productClass 的构造函数返回类型

梳理到这里就相当简单了,我们来看看最终成品

typescript 复制代码
type ConstructorInstanceType<T extends new (...args: any[]) => any> =
  T extends new (...args: any[]) => infer R ? R : any;

// 白名单工厂类
export default class ValidateFactory<T extends new (...args: any[]) => any> {
  private productClass;

  constructor(productClass: T) {
    this.productClass = productClass;
  }

  async create(...args: ConstructorParameters<T>): Promise<ConstructorInstanceType<T> | null> {
    let productObj: ConstructorInstanceType<T> | null = null;
    const available = await this.validateAvailable();
    if (available) {
      productObj = new this.productClass(...args);
    }
    return productObj;
  }
  
  // ... 其他校验函数
}

效果也是相当哇塞,终于不再是三无产品了,使用过程中也会有相关的提示

小结

到这里这个故事就讲完了,工作本身虽然是螺丝钉,但是愿意去思考发现的话,还是会有很多可以优化,苦中作乐的事情的。有问题的同学,也欢迎在评论区提问交流~

相关推荐
m0_748255261 小时前
前端安全——敏感信息泄露
前端·安全
鑫~阳3 小时前
html + css 淘宝网实战
前端·css·html
Catherinemin3 小时前
CSS|14 z-index
前端·css
2401_882727574 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
NoneCoder4 小时前
CSS系列(36)-- Containment详解
前端·css
anyup_前端梦工厂5 小时前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand5 小时前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL5 小时前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿5 小时前
react防止页面崩溃
前端·react.js·前端框架
z千鑫5 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js