思考为什么要用工厂模式

以 JavaScript 为例子发表一下我对工厂模式 (Factory Pattern) 的看法

为什么不直接 new 呢? 我自己写代码直接 new 自己写的类不就好了么?

这里不得不提一下设计模式的意义。设计模式并不是凭空捏造的,而是长期开发所形成的经验之谈,当然这些经验自然是成功的,被认可的经验,否则设计模式就不会被那么多人认可和推崇了。

设计模式主要针对的是大项目,也就是多个人的项目。A 并不一定知道 B 写了哪些类。通过工厂模式,A 在调用工厂类生产的实例的时候就不用关心 B 实现了哪些类了。

举例:

B 写的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Factory {
static create (type) {
if (type === 1) {
return new A()
} else if (type === 2) {
return new B()
} else {
return new C()
}
}
}

class A { execute() {} /* ... */}
class B { execute() {} /* ... */}
class C { execute() {} /* ... */}

A 写的代码:

1
2
3
4
const foo = Factory.create(1)
const bar = Factory.create(2)
await foo.excute()
await bar.excute()

如代码所示,A 只要知道返回回来的实例具有 execute 方法就足够了,不用去记住各种类名, 甚至没有感觉到自己拿到的其实是不同的类。同时,因为没有直接使用 new 具体的某个类,所以如果以后要修改类名的话就会简单很多 – 比如要把 A 改名为 Ace,那么只需额外在 Factory 里把 A 改为 Ace 就行了。

一般什么情况比较适合用工厂模式?

一般需要根据外界的一些动态环境选择具体实例哪些类,或者说具体选择实例哪个类需要很繁杂的判断。

1
2
3
4
5
6
7
8
class Factory {
static create (config) {
if (config.version <= 2) return new BasicApp()
if (config.os === 'linux') return new BashApp()
if (config.os === 'windows' && config.online) return new OnlineApp()
else return new LocalApp()
}
}

如代码所示,如果要决定实例哪个类要许多复杂的判断,那么用工厂模式封装就会轻松很多。需要做判断的事情已经交给了写 Factory 的人,使要实例的开发者也不用去了解判断规则, 并且这在未来修改判断规则提供了便利。

另外,有些人认为 new 一个具体的实例也是种硬编码。如果在一个项目里太多的地方用 new, 比如 new Foo()。那么 Foo 就与项目的多个地方有了紧密的联系。而为了使联系不那么紧密 (decouple),将工厂作为两者之间的一个缓冲,使具体的类和需要类的地方被间接地连起来。我个人认为这个有一定道理,在一些大项目上可以考虑,但在小项目中似乎显得没那么必要。因为就算要改名,用 IDE 就可以替换掉几个文件中的所要替换的类名。

使用工厂模式有什么注意的么?

其实如上面看到的一样,Factory 返回回来的实例是实现了相同接口 / 功能的,因为这样才能使调用 Factory 的开发者真地只需要关心功能而可以忽略用了自己拿到的是具体什么类。例如,在 Java 之类的语言中,一般有大量的子类是继承了某抽象类,或实现了某接口,以此保证具有同一的方法名。

如果返回回来的实例有实现功能的不同接口,那么工厂模式的效用将大打折扣。

1
2
3
4
5
6
7
8
9
10
11
class Factory {
static create (type) {
if (type === 1) return new A()
if (type === 2) return new B()
if (type === 3) return new C()
}
}

class A { print() {} }
class B { output() {} }
class C { echo() {} }

如代码所示,虽然 A, B, C 都实现了输出的功能。但是方法名不同,这导致多次调用 Factory 的开发者不得不时时刻刻提醒自己同样是从 Factory 拿到的对象为什么会有不同。这种设计或多或少会带来潜在的 bug。

什么时候不要用工厂模式?

设计模式是好东西,但不是万能的,只有特定的场合解决特定的问题。一般来讲,如果你的开发场景中没有出现本文前面所讲的 复杂的实例过程相同的接口 的话,我个人就不建议使用了。

参考

  1. Learning JavaScript Design Patterns
  2. 知乎: 工厂设计模式有什么用?
  3. 知乎: 工厂模式(factory Method)的本质是什么?为什么引入工厂模式
  4. Factory Pattern. When to use factory methods?
  5. Exploring the Factory Design Pattern