51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

JS设计模式之工厂模式

工厂方法模式 {#工厂方法模式}

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类

------《设计模式:可复用面向对象软件的基础》中文版第81页

在我理解中,所谓工厂方法,是指我们通过调用已知的接口,获得未知的对象,做出预期的行为。工厂方法为我们提供这一对象。试想一下,如果我们有将当前网页分享给QQ或者微信的需求,可以通过以下代码实现:

|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class Sharer { constructor({msg, link}) { this.msg = msg this.link = link } share() {} } class WeChatSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via WeChat`) } } class QQSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via QQ`) } } class SharerFactory { create(options) {} } class QQSharerFactory extends SharerFactory { create(options) { return new QQSharer(options) } } class WeChatSharerFactory extends SharerFactory { create(options) { return new WeChatSharer(options) } } function share(factory, options) { factory.create(options).share() } share(new QQSharerFactory(), { msg: 'msg1', link: 'https://example.com' }) // Sharing msg1 from https://example.com via QQ share(new WeChatSharerFactory(), { msg: 'msg2', link: 'https://baidu.com' }) // Sharing msg2 from https://baidu.com via WeChat |

其实JS中大可不必这样做,直接传构造函数为参数就可以,因为JS中函数是一等公民。这里列出代码只是用于学习这个模式。

抽象工厂模式 {#抽象工厂模式}

提供一个接口以创建一系列相关或相互依赖的对象,而无需指定它们具体的类。

------《设计模式:可复用面向对象软件的基础》

说人话,就是工厂方法只有一个方法,抽象工厂有多个方法。许多文章喜欢以不同的操作系统匹配不同的外观举例,但是我们既然都用上了跑在浏览器里的JS,那就尽可能不考虑跨平台的问题。

所以我想到了移动端和桌面端UI不同,这或许是一个应用抽象工厂模式的良好切入点。以下列代码为例:

|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class UIFactory { createNavbar() {} createContent() {} } class MobileUIFactory extends UIFactory { createNavbar() { return { position: 'top' } } createContent() { return { direction: 'vertical' } } } class DesktopUIFactory extends UIFactory { createNavbar() { return { position: 'left' } } createContent() { return { direction: 'horizontal' } } } function getFactory() { if (window.innerWidth < 1000) { return new MobileUIFactory() } return new DesktopUIFactory() } const factory = getFactory() factory.createContent() factory.createNavbar() |

当然这些代码没有什么实际作用,但是这就是抽象工厂模式的思想:创建一系列相关的对象。

可拓展的工厂 {#可拓展的工厂}

其实在 Learning JavaScript Design Patterns 中所实现的抽象工厂模式,就是一个可拓展的工厂。如果按照《设计模式:可复用面向对象软件的基础》中的定义来说,它是不正确的。但我们不探讨者是否正确,我们也来实现一个可拓展的工厂。

通过Map实现 {#通过Map实现}

其实是通过对象,但是为了与前面所说的对象做区分,这里用Map映射这个说法。代码如下,还是实现一个分享的功能:

|---------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class Sharer { constructor({msg, link}) { this.msg = msg this.link = link } share() {} } class WeChatSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via WeChat`) } } class QQSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via QQ`) } } class Factory { types = {} register(type, cls) { this.types[type] = cls } create(type, options) { const cls = this.types[type] return cls ? new cls(options) : undefined } } const factory = new Factory() factory.register('qq', QQSharer) factory.register('wechat', WeChatSharer) factory.create('qq', { msg: 'msg1', link: 'https://example.com' }).share() // Sharing msg1 from https://example.com via QQ factory.create('wechat', { msg: 'msg2', link: 'https://baidu.com' }).share() // Sharing msg2 from https://baidu.com via WeChat |

我们通过给register方法传递一个构造函数(用class关键字定义的)来注册一个类,之后就可以通过create方法来创建它。

通过重写方法实现 {#通过重写方法实现}

通过继承,先检查是否有自己拓展的类型,之后再通过super调用到父类的方法,代码如下:

|---------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | class Sharer { constructor({msg, link}) { this.msg = msg this.link = link } share() {} } class WeChatSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via WeChat`) } } class QQSharer extends Sharer { share() { console.log(`Sharing ${this.msg} from ${this.link} via QQ`) } } class Factory { create(type, options) { if (type === 'qq') { return new QQSharer(options) } } } class ExpandedFactory extends Factory { create(type, options) { if (type === 'wechat') { return new WeChatSharer(options) } return super.create(type, options) } } const factory = new ExpandedFactory() factory.create('qq', { msg: 'msg1', link: 'https://example.com' }).share() // Sharing msg1 from https://example.com via QQ factory.create('wechat', { msg: 'msg2', link: 'https://baidu.com' }).share() // Sharing msg2 from https://baidu.com via WeChat |

我个人推荐通过Map实现这个需求,因为这样写的不好之处在于它想要拓展就需要重新定义一个类,然后去重写父类的方法,等等。

总结 {#总结}

下面来自于 Learning JavaScript Design Patterns,我手动翻译过来的。

什么时候需要使用 {#什么时候需要使用}

工厂模式在下列几种情况是很有用的:

  • 当创建对象或初始化的过程很复杂时。
  • 当我们需要根据当前环境创建不同的对象时。
  • 当我们操作很多拥有共同属性的小对象时。
  • 当我们利用duck typing时,它可以很方便的用来解耦。

什么时候不要使用 {#什么时候不要使用}

  • 由于JS的动态类型,运用工厂方法可能会导致复杂的类型问题。如果你没有提供一个统一的接口,推荐直接使用new创建对象(TypeScript完美解决)。
  • 由于对象的创建被隐藏起来了,所以进行单元测试的时候比较困难。

参考 {#参考}

设计模式:可复用面向对象软件的基础

Learning JavaScript Design Patterns -- The Factory Pattern

赞(2)
未经允许不得转载:工具盒子 » JS设计模式之工厂模式