故事的主人公叫小 M ,他一直想在工作之外再赚点钱,某一天,他突发奇想,想去开一家网店,卖什么呢?最近新发布的 13 香 iPhone、iPad,肯定很多人买,那我去卖保护壳?肯定大卖,说干就干,第二天他就把自己的网店支愣起来了。
虽然网店申请好了,但是他的产品还没设计好,于是乎,他开始当「架构师」,设计他的产品。
一口吃不成个胖子,他想先让他家的保护壳可以自定义颜色和材质,为了方便所有的保护壳使用,他做了一个父类,实现了设置颜色、设置材质这两个功能。
type Color = 'black' | 'red' | 'green';
type Material = '橡胶' | '皮质' | '硬质塑料'
class 保护壳 {
private color: Color = 'black'
private material: Material = '橡胶'
constructor() {
// ...
}
设置颜色(color: Color) {
this.color = color
}
设置材质(material: Material) {
this.material = material;
}
whoIAm() {
console.log('我是保护壳')
}
}
设置好了这个保护壳父类后,继承这个父类的 iPhone 保护壳和 iPad 保护壳的代码就特别少,小 M 洋洋得意说:看我的设计水平多高,以后我的店做大了,想做小米手机的、华为手机的,根本不需要写代码,只需要继承一下就好!
class IPhone extends 保护壳 {
whoIAm() {
console.log('我是 iPhone 保护壳')
}
}
class IPad extends 保护壳 {
whoIAm() {
console.log('我是 iPad 保护壳')
}
}
刚好,有用户下单说,我想要一个红色、皮质的 iPad 保护壳,小 M 直接就用刚才建的 Ipad
类运行下面两行代码,嗖的一下就做好了:
const a = new IPad()
a.设置材质('皮质')
a.设置颜色('red')
随着时间的推移,他家的网店慢慢做大了,又支持了小米手机、小米平板、华为手机、华为平板等数十种产品。
做好了这一步,小 M 并不满足,我那只是一个开始,为了能占领某某平台的榜首,我需要我的保护壳与众不同!
先让保护壳支持用户 DIY 吧?
怎么实现呢,小 M 想,这简单呀,就在父类加一个 DIY 方法就好了,这样我们店里卖的全部保护壳就都能一下支持 DIY 了:
class 保护壳 {
...
DIY() {
console.log('我是父类的 DIY')
// 用户自己 DIY
}
...
}
确实,这样以来,所有保护壳的子类都能进行 DIY 了。但是这只是短暂的黎明。
就在此时,小 M 遇到了开网店的第一个打击,虽然它设计好了方案,但是工厂那里由于原材料不够,暂时不能让 iPad 支持 DIY,而我们的 DIY 方法加到了所有的保护壳中,要赶紧的下线掉。
怎么办呢?小 M 想,这可难不倒我,我直接让 iPad 保护壳的类里,写一个空的 DIY
方法,覆盖一下父类的的 DIY 方法就好。
class IPad extends 保护壳 {
DIY() {
// 什么也不做,只用了覆盖
}
whoIAm() {
console.log('我是 iPad 保护壳')
}
}
这次问题算是解决了,但是又有问题了。
有一位顾客要求华为平板保护壳的 DIY 功能更丰富一点。没办法,顾客是爹呀,这时候 小 M 不得不找这个保护壳,按照他的要求修改 DIY 函数。
后来那位顾客的一个朋友发现,你的保护壳不错哎,但是他使用的是 iPad ,所以,也想在 iPad 里面有一样的 DIY 功能,小 M 又得去 iPad 的子类里面去修改。
这种情况经常发生,他按照上面的方法在每个子类里面改来改去,发现重复代码越来越多,但是又很难复用。
终于有一天,他忍受不了每次都要去子类里面修改的痛苦了,决心要修改一下代码的架构。
他发现 DIY 这个行为经常因为各种原因被修改,所以第一步,他打算先把这个行为抽象出来:
type DeviceType = 'mi-11' | 'mid-pad-5' | 'iphone' | 'ipad'
interface DIYBehavior {
DIY: () => void
}
接下来他根据可不可以 DIY,做了两个实现了这个接口的类。
如果当前可以 DIY,就是这个类:
class CanDIY implements DIYBehavior {
private deviceType: DeviceType
constructor(deviceType: DeviceType) {
this.deviceType = deviceType;
}
DIY() {
console.log(`我要对这个${this.deviceType}进行 DIY`)
}
}
当不能 DIY 的时候,就用这个:
class CanNotDIY implements DIYBehavior {
private deviceType: DeviceType
constructor(deviceType: DeviceType) {
this.deviceType = deviceType;
}
DIY() {
console.log(`目前对这个${this.deviceType}不能进行 DIY`)
}
}
我们完全把 DIY 这个行为的逻辑抽离了出去,那怎么用呢?
假如,目前我们的 iPad 平板支持 DIY,我们就可以按照如下的方式组织我们的 iPad 类:
class IPad extends 保护壳 {
private ipadDIY: DIYBehavior
constructor() {
super();
this.ipadDIY = new CanDIY('ipad')
}
DIY() {
this.ipadDIY.DIY()
}
...
}
也就是说,我们把 DIYBehavior
作为了我们保护壳的一个属性,只要是进行 DIY,就通过这个属性来做。
改成这样之后,我们设想一下,假如我们的平板的 DIY 的功能又增强了,支持了各种酷炫的功能,以前的设计我们可能要把每个支持 DIY 子类的 DIY 方法都改一下。
但是现在,我们完全不需要改动它们,我们只需要改动 CanDIY
这个类的 DIY
方法就好了!
不仅如此,如果我们某个类突然不支持 DIY 功能了,没关系,我们可以增加一个 setter
,运行时修改这个变量:
class IPad extends 保护壳 {
private _ipadDIY: DIYBehavior
constructor() {
super();
this._ipadDIY = new CanDIY('ipad')
}
set ipadDiY(ipadDiY: DIYBehavior) {
this._ipadDIY = ipadDiY;
}
DIY() {
this._ipadDIY.DIY()
}
whoIAm() {
console.log('我是 iPad 保护壳')
}
}
const a = new Ipad()
a.ipadDiY = new CanNotDIY('ipad')
a.DIY() // 目前对这个ipad不能进行 DIY
有没有感觉这样子组织方便、灵活了好多。我们再也不用担心 DIY 这个行为对我们的各个子类有别的什么影响了。
上面我们使用到的设计模式就是策略模式,下面是比较官方的定义,大家大概明白意思就好,反正感觉有点不像「人话」。
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中华民国交个人所得税”就有不同的算税方法。 —— 维基百科
不知道大家看了上面的例子,在看这个定义是不是更理解了一点。
总结来说,我们可以把一些「行为」抽离出来,单独维护,以便我们可以灵活的对这些「行为」进行改动。
举一个比较常见的例子,如果我们想开发一个缓存模块,在这个缓存模块里的缓存有内存级别、session 级别、localStorge 级别,此时对于设置缓存、清除缓存的方法,我们就可以借助策略模式,把他们抽离出来。
另外,我们的 React 中一般会调用 this.setState
来更新视图,这个方法可以在 ReactDOM 下使用,也可以在 ReactNative 下使用,这是因为 React 中的更新器(updater)会根据平台的不同而不同,任你以后多加几个平台,只要实现了更新器规定的接口,就能正常的使用 React。这里的设计也是使用了策略模式。
在上面的示例中,我们实现 DIYBehaviour
这个接口使用的是类,这在其他语言中是很自然的,但这有点不太符合在 JavaScript 中使用习惯,我们在理解了策略模式后,遵循着它的准则,我们也可以使用函数来实现,这可能用起来更舒服、方便一点:
function canDiY(deviceType: DeviceType): DIYBehavior {
return {
DIY() {
console.log(`我要对这个${deviceType}进行 DIY`)
}
}
}
class IPad extends 保护壳 {
private _ipadDIY: DIYBehavior
constructor() {
super();
this._ipadDIY = canDIY('ipad') // 调用形式稍微改一下
}
...
}
到这里,小 M 终于开心的完成了对这段代码的重构,我们的故事就结束了。
谢谢各位的阅读,同时祝各位中秋假期快乐。