上周有一个任务是在一个组件中增添一个钱包的支持,虽看懂代码的表层逻辑,却懵懂代码引入的外层逻辑以及模块接口功能,于是也不敢胡乱重构原代码,而是直接在流程开始时和事件方法内添加 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()
}
也就是说只需要将 payType
、payAddress
、payAmount
输入 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!🥳
A 和 B 是不同独立文件,本来将父类 X 放在了工厂函数文件内,后来发现报错,搜索发现应该是模块的循环加载导致的问题,于是把 X 也写成了独立文件。整个重构过程中自然将整个原组件的内外逻辑都吃透了。 ↩︎