
架构师李四干了个啥?
我们先思考下面的场景:
新项目来了,技术总监 开始排任务了,需要 张三 同学来实现采购单的功能,包含采购单的审核、拒绝等功能。于是 张三 编写了下面的代码:
/**
* 数据基础模型
* @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%”,张三一气之下直接原地离职了……离职了……职了……了。。。
第二周,技术总监 招聘了一个新的架构师 李四,请记住这个名字,架构师李四 :)
这位新架构师一来就发现问题了:
接下来业务肯定会扩展,说不定还需要实现 出库单、入库单、销售单 等各种单据,每种单据都有审核拒绝的功能,于是技术总监让 李四 来封装个公共服务给大伙用,并且建立的下面的新规范:
- 审核和拒绝的方法名必须为
audit和reject - 审核和拒绝都只传入单据的主键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写前端”。