工厂模式

上周有一个任务是在一个组件中增添一个钱包的支持,虽看懂代码的表层逻辑,却懵懂代码引入的外层逻辑以及模块接口功能,于是也不敢胡乱重构原代码,而是直接在流程开始时和事件方法内添加 if 来判断执行自己的新增代码,模仿原代码的方法写几个新方法,比如 check() 改为 checkA()checkB(),具体来说:

methods: {
  checkA() {
    // do something
  }

  checkB() {
    // do something new
  }

  // ...

  submit() {
    if (this.payType === 'B') {
      this.checkoutB()
    } else {
      this.checkoutA()
    }
  }
},
created() {
  if (this.payType === 'B') {
    this.checkB()
  } else {
    this.checkA()
  }
}

很明显,代码虽然工作但是不够简洁,需要在每个事件中都添加相同的 if,未来如果要扩展新的钱包,修改就会比较麻烦,而且会导致这个组件越来越臃肿,进而越来越难以维护。尽管如此,时间紧迫功能要紧所以还是硬着头皮把修改推了上去,然后自然地被要求抽出单独的模块,写成一个类。


只得努力想了想应该怎么去实现,回想起了一些类的知识,搜索浏览了几篇又看了看眼前的代码还是懵到头大。此时脑中闪过在 GitHub 阅过的一行代码,于是潇洒掉头,何必困于具体实现呢,先来设计实现后的调用接口吧!于是将组件内原代码的方法都删了个干净,留下了:

methods: {
  submit() {
    this.wallet.checkout()
  }
},
created() {
  this.wallet = new Wallet(this.payType, this.payAddress, this.payAmount)
  this.wallet.init()
}

也就是说只需要将 payTypepayAddresspayAmount 输入 Wallet,然后初始化实例 wallet,实现组件内调用实例的相同方法,就能完成相同的功能(输出)。这样将组件内的 methods 抽出独立的 utils 以后,以后新增钱包时这个组件是无需修改的。那关键是怎么通过类去实现根据 payType 的不同实现相同方法的不同内容呢?注意这与相同方法根据 payType 的不同实现不同内容的区别,后者需要在多个 methods 里面使用 if,是不符合设计理念的。

可就在这时,被询问进度怎么样?于是一阵挠头,指向我设计好却尚不知如何去实现的代码,然后听从建议先让原方法工作,将整个流程走通。看着刚被 Merged 的代码,还是有些不甘,于是往 Todos 里面默默加了一项。


晚上回来后,继续上面的构想,想着怎么去实现,想了想试了试还是不行。于是去请教前同事关于这种问题的设计模式,得到了回答——工厂模式。

搜索阅读一番后,最终成功实现了!尽管与最先设计的接口有些不同……通过工厂函数判断 payType 初始化不同类 A 或 B 的实例,A 和 B 有相同名字的 methods,是原组件要实现功能的抽象拆分。后来发现 A 和 B 有相同内容的 methods,于是再次将它们抽成了一个 A 和 B 的父类 X[1]

// components/unknown.vue
import { WalletFactory } from '../utils/wallet'

methods: {
  submit() {
    this.wallet.checkout()
  }
},
created() {
  this.wallet = WalletFactory.createWallet({
    payType: this.payType,
    payAddress: this.payAddress,
    payAmount: this.payAmount,
    scope: this
  })
  this.wallet.init()
}
// utils/wallet/index.js
import { A } from './a'
import { B } from './b'

export const WalletFactory = {
  createWallet: (o) => {
    switch (o.payType) {
      // B
      case 'B':
        return new B(o)
      // Others
      default:
        return new A(o)
    }
  }
}
// utils/wallet/a.js
import { Wallet } from '../wallet/wallet'

export class A extends Wallet {
  constructor(o) {
    super(o)
  }

  // ...

  checkout() {
    // do something
  }
}
// utils/wallet/b.js
import { Wallet } from '../wallet/wallet'

export class B extends Wallet {
  constructor(o) {
    super(o)
  }

  // ...

  checkout() {
    // do something new
  }
}
// utils/wallet/wallet.js
export class Wallet {
  constructor(o) {
    this.o = o
  }

  // ...

  init() {
    // do something via same named methods
  }
}

最后 git add && git commit && git push,GitHub 提 PR,Merged!🥳


  1. A 和 B 是不同独立文件,本来将父类 X 放在了工厂函数文件内,后来发现报错,搜索发现应该是模块的循环加载导致的问题,于是把 X 也写成了独立文件。整个重构过程中自然将整个原组件的内外逻辑都吃透了。 ↩︎