架构师李四在前端用适配器都干了啥

2,366 阅读5分钟

banner.jpg

架构师李四干了个啥?

我们先思考下面的场景:

新项目来了,技术总监 开始排任务了,需要 张三 同学来实现采购单的功能,包含采购单的审核、拒绝等功能。于是 张三 编写了下面的代码:

/**
 * 数据基础模型
 * @author 张三
 */
class BaseModel{
  id!: number
}
/**
 * 采购单数据模型
 * @author 张三
 */
class PurchaseBillModel extends BaseModel {
  code!: string

  status!: PurchaseBillStatus
}

/**
 * 采购单API
 * @author 张三
 */
class PurchaseBillApi extends AbstractBaseService {

  baseUrl = "purcharse"

  async confirm(bill:PurchaseBillModel):Promise<void>{
    // 发起网络请求 确认传入的采购单 
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    const model = new PurchaseBillModel()
    model.id = bill.id
    await new Http("audit").post(model)
  }

  async reject(bill:PurchaseBillModel):Promise<void>{
    // 发起网络请求 拒绝传入的采购单
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    const model = new PurchaseBillModel()
    model.id = bill.id
    await new Http("reject").post(model)
  }

  // 省略其他方法
}

// 使用 
const purchaseBill // 从其他地方获取的采购单数据
const purchaseBillApi = new PurchaseBillApi()
purchaseBillApi.confirm(purchaseBill)
purchaseBillApi.reject(purchaseBill)

写完提测,且不说 张三 写得如何,但也好在发布上线,美滋滋。

然后第二天,张三所在的公司发通知说,“因为经营不善,全员降薪40%”,张三一气之下直接原地离职了……离职了……职了……了。。。

第二周,技术总监 招聘了一个新的架构师 李四,请记住这个名字,架构师李四 :)

这位新架构师一来就发现问题了:

接下来业务肯定会扩展,说不定还需要实现 出库单、入库单、销售单 等各种单据,每种单据都有审核拒绝的功能,于是技术总监让 李四 来封装个公共服务给大伙用,并且建立的下面的新规范:

  • 审核和拒绝的方法名必须为 auditreject
  • 审核和拒绝都只传入单据的主键ID字段
  • 调用后端服务的类都以 Service 结尾,不再使用 Api 结尾

架构师李四 略加思考,简单实现如下:

/**
 * 单据基础服务
 * @author 李四
 * @apiNote 需要统一的类都实现这个接口
 */
class AbstractBillService<T extends BaseModel> extends AbstractBaseService implements IBillService<T>{
  /**
   * 获取一个只包含ID字段的模型
   * @param id 单据ID
   */
  getModelOnlyId(id: number){
    const model = new BaseModel()
    model.id = id;
    return model;
  }

  async audit(billId: number):Promise<void>{
    // 发起网络请求 审核传入的销售单
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    await new Http("audit").post(getModelOnlyId(billId))
  }

  async reject(billId: number):Promise<void>{
    // 发起网络请求 拒绝传入的销售单
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    await new Http("reject").post(getModelOnlyId(billId))
  }

  // 省略更多公共方法
}

/**
 * 出库单数据模型
 * @author 李四
 */
class OutputBillModel extends BaseModel {
  code!: string

  status!: OutputBillStatus
}

/**
 * 出库单服务
 * @author 李四
 */
class OutputBillService extends AbstractBillService<OutputBillModel> {
  baseUrl = "output"
  
  // 省略其他方法
}

// 使用
const outputBill // 从其他地方获取的出库单数据
const outputBillService = new OutputBillService()
outputBillService.audit(outputBill.id)
outputBillService.reject(outputBill.id)

人不够用了,又招了个 王五,来负责写入库单、销售单等业务逻辑。于是 王五 开始写入库单:

/**
 * 入库单数据模型
 * @author 王五
 */
class InputBillModel extends BaseModel {
  code!: string

  status!: InputBillStatus
}

/**
 * 入库单服务
 * @author 王五
 */
class InputBillService extends AbstractBillService<InputBillModel> {
  baseUrl = "input"
  
  // 省略其他方法
}

// 使用
const inputBill // 从其他地方获取的入库单数据
const inputBillService = new InputBillService()
inputBillService.audit(inputBill.id)
inputBillService.reject(inputBill.id)

架构师李四 发现了这些问题:

  • 张三 的代码中,审核的方法名写的是 confirm
  • 张三 要求传入的不是 ID 字段,而是整个单据对象
  • 新来的 赵六 需要调用 张三 的审核和拒绝方法,一直骂骂咧咧说 张三 不尊重规范
  • 架构师李四 心里也清楚,张三 的屎山去不掉了,他自己也恶心得不想碰
  • 技术总监 说,已经测试过的代码就别碰了

这可咋整呢? 架构师李四 很难受。想想办法,既然 张三 不尊重规范,那我们就写个适配器来适应我们的新规范,而且在保持不碰 张三 代码的前提下,于是 架构师李四 很快就想出了下面的招:

/**
 * 采购单服务
 * @author 李四
 */
class PurchaseBillService extends AbstractBillService<PurchaseBillModel> {

  baseUrl = "purcharse"

  // 作为适配器,需要把张三的审核和拒绝方法适配过来
  private purchaseBillApi = new PurchaseBillApi()

  /**
   * 重写审核方法
   */
  async audit(billId: number):Promise<void>{
    // 调用张三写完的审核方法 
    const purchaseBill = new PurchaseBillModel()
    purchaseBill.id = billId
    return purchaseApi.confirm(purchaseBill)
  }

  /**
   * 重写拒绝方法
   */
  async reject(billId: number):Promise<void>{
    // 调用张三写完的拒绝方法
    const purchaseBill = new PurchaseBillModel()
    purchaseBill.id = billId
    return purchaseApi.reject(purchaseBill)
  }
  // 省略其他方法
}

好,到这里,所有的适配工作就都完成了。所有的单据都有了相同的功能。即使有特殊的需求,也可以重写 AbstractBillService 提供的 audit/reject,但至少是保证了调用的规范。后续写的代码也不再继续去调用 张三 的审核和拒绝方法,而是直接调用 PurchaseBillService 作为 适配器 适配之后的 审核和拒绝 方法。

但适配总归是适配:

  • 如果时间合适,或者有兴趣分析 张三 代码的新同学来了,可以去将 张三 的代码移植到新的 Service 中,然后慢慢的移除掉 张三 的屎山;
  • 如果没有人有兴趣,那就去给 张三 的方法标记弃用吧,至少新代码不用再去耦合 张三 的屎山了,像下面这样:
/**
 * 采购单API
 * @author 张三
 */
class PurchaseBillApi extends AbstractBaseService {

  baseUrl = "purcharse"

  /**
   * @deprecated
   * @apiNote 你好,我是赵六,张三的这些屎山代码已经不再建议你调用
   * @see PurchaseBillService.audit(billId: number)
   */
  async confirm(bill:PurchaseBillModel):Promise<void>{
    // 发起网络请求 确认传入的采购单 
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    const model = new PurchaseBillModel()
    model.id = bill.id
    await new Http("audit").post(model)
  }

  /**
   * @deprecated
   * @apiNote 你好,我是赵六,张三的这些屎山代码已经不再建议你调用
   * @see PurchaseBillService.reject(billId: number)
   */
  async reject(bill:PurchaseBillModel):Promise<void>{
    // 发起网络请求 拒绝传入的采购单
    // 假设这里的逻辑很复杂 不仅仅是发个网络请求这么简单
    const model = new PurchaseBillModel()
    model.id = bill.id
    await new Http("reject").post(model)
  }

  // 省略其他方法
}

全剧终

相关前端面向对象开发的源代码可以参考:

Github: github.com/HammCn/AirP…

Gitee: gitee.com/air-power/A…

当然,也欢迎你阅读我的专栏:“用TypeScript写前端”。