此篇文章仅作为一篇读后感,方便个人记忆,如果需阅读更加详细以及权威的内容,移步史上最全设计模式导学目录
工厂,顾名思义是一个生产产品的地方,在设计模式中,他是一个生产其他类的实例的类。
工厂有三兄弟,分别是:
- 简单工厂模式
- 工厂模式(又名:虚拟构造器模式)
- 抽象工厂模式
他们在复杂度上层层递进,在对复杂场景的支持能力上也是层层递进的,下面将借助实例来描述每一个兄弟的定义,优缺点,和使用场景。
简单工厂模式
我在策略+简单工厂模式实践
这篇文章中使用了简单工厂模式,根据载体类型的不同,生产对应的转换器。
class ConvertorFactory{
static getConvertor(type){
if(type === "excel"){
return new ExcelConvertor()
}
if(type === "vfs"){
return new VfsConvertor()
}
}
}
const excelConvertor = ConvertorFactory.getConvertor('excel');
简单工厂模式确实是很简单,但是在这个案例里也足够用了。
简单工厂模式的优点主要有两个:
- 分离了创建实例和使用实例这两个职责。在没有工厂之前,既需要创建载体对应的转换器实例,还需要调用转化器上的转换方法,而有了工厂之后,所有创建的工作都交给工厂,职责更加清晰;
- 调用者只需要关心它需要什么类型的产品,具体如何生产这些产品不需要其考虑。在我的Case中,只需要告诉工厂type到底是Excel还是VFS即可,具体如何创建是不需要感知的。
不难看出,这两个有点在我的Case中都发挥了出来,看起来十分完美,但是简单工厂并不能完全覆盖所有的场景。
不妨设想:
- 我们在转换vfs数据时,需要调用者传入用户名和密码是否能正确地访问到vfs数据中指定的路径,如果不能访问,则创建转换器失败;
- 我们在转换excel数据时,需要调用者传入支持Excel版本号来确定对否能转换, 如果不能访问,则创建转换器失败。
这时,我们使用简单工厂方法就难办到了,因为作为调用者,不仅需要知道载体类型,还要提供对应的参数才能拿到实例。
工厂模式
而工厂模式正是为了解决这个问题而出现,直接来看看如果采用工厂模式如何做:
// 抽象产品(转换器)
// 实际中我用的是interface, 为了更加还原刘老师的例子这里用抽象类
abstract Convertor{
abstract digitalize(){}
abstract visualize(){}
}
// 具体产品(Excel转换器)
class ExcelConvertor extends Convertor{
digitalize(){
...
}
visualize(){
...
}
}
// 具体产品(VFS转换器)
class VfsConvertor extends Convertor{
digitalize(){
...
}
visualize(){
...
}
}
// 抽象工厂
abstract ConvertorFactory{
createConvertor(){}
}
// 具体工厂
class ExcelConvertorFactory extends ConvertorFactory{
createConvertor(versions){
if(valid(versions)){
return new ExcelConvertor();
}else{
throw Error("Cannot acess excel")
}
}
}
class VfsConvertorFactory extends ConvertorFactory{
createConvertor(username, credential){
if(valid(username, credentials)){
return new VfsConvertor();
}else{
throw Error("Cannot acess fold")
}
}
}
// client
factory: ConvertorFactory = new ExcelConvertorFactory();
factory.createConvertor(['v1', 'v2'])
从以上案例可以看出,具体工厂和具体产品是一对一出现的,每个工厂只负责生产一种产品,但是每个工厂都继承自抽象工厂,所以他们都实现了同一个方法: createConvertor.
工厂模式的优点主要有两点:
- 调用者完全面向工厂,他不需要像简单工厂模式中那样,在一个中心化的管家那里传入类型来获取实例,现在如果需要某种产品,就去找那个产品对应的工厂来生产;
- 由于多态性(也就是声明为父类类型创建的是子类的实例),当需要新增产品时,只需要新增一个具体产品和具体工厂即可,不需要改动其他代码。
同简单工厂方法一样,工厂方法也不能cover掉所有的场景。上文说到,每个具体工厂仅负责生产一种具体产品,那么当有多个产品他们具有一些共性时,如果依旧为他们每种产品都创建工厂,那么工厂类的数量将急剧上升。
这里,我不再基于已有CASE编例子了,而用刘老师在2 产品等级结构与产品族提到的电视机、电冰箱、空调的例子,加以不切实际的想象来作为案例。
假设需要采购一批家电,包括电视机、电冰箱两种;品牌可从美的和海尔里面选择,但是为了拿到更低的采购价,要么全部都从海尔买,要么全部都从美的买。要求可以随时切换品牌算出总价。
如果我们不做任何改进,依旧使用工厂模式:
abstract class Haier(){
getBrand(){
return “I am Haier”
}
abstract getPrice():number
}
class HaierTV extends Haier(){
getPrice(){
return 1000;
}
}
class HaierFrige extends Haier(){
getPrice(){
return 1500;
}
}
abstract class Midea(){
getBrand(){
return “I am Midea”
}
getPrice(): number
}
class MideaTV extends Midea(){
getPrice(){
return 1000;
}
}
class MideaFrige extends Midea(){
getPrice(){
return 1500;
}
}
abstract class Factory{
abstract create(){}
}
// 由于有4种产品,所以需要4个工厂
class class MideaFrigeFactory extends Factory{
create(){
return new MideaFrige()
}
}
// MideaTVFactory, HaierTVFactory, HaierFrigeFactory就省略了,要不然太长了
// Client
function calculator(){
const fridgeFactory: Factory = new MideaFrigeFactory();
const tvFactory: Factory = new MideaTVFactory();
return fridgeFactory.create().getPrice()*10
+ tvFactory.create().getPrice*6
}
代码很长很痛苦,并且当我想增加一个新品牌格力的时候,我需要新建:GreeTVFactory, GreeFrigeFactory, GreeTV, GreeFrige四个新的类。
然而,不管是什么品牌,始终都是那两样电器,其实不需要那么多工厂,所以能不能让一个一个品牌的工厂既生产电视机,又生产冰箱,还能生产空调呢?当然能!
抽象工厂模式
先来直接看看如果用抽象工厂模式如何来做
abstract class Haier(){
getBrand(){
return “I am Haier”
}
abstract getPrice():number
}
class HaierTV extends Haier(){
getPrice(){
return 1000;
}
}
class HaierFrige extends Haier(){
getPrice(){
return 1500;
}
}
abstract class Midea(){
getBrand(){
return “I am Midea”
}
getPrice(): number
}
class MideaTV extends Midea(){
getPrice(){
return 1000;
}
}
class MideaFrige extends Midea(){
getPrice(){
return 1500;
}
}
// 以上抽象产品和具体产品都没变
// 现在建一个抽象工厂,该工厂既可以生产电视机,也可以生产冰箱
abstract class Factory{
abstract createTV();
abstract createFrige();
}
// 然后开始建海尔工厂和美的工厂
class HaierFactory extends Factory{
createTV(){
return new HairTV()
}
createFrige(){
return new HairFrige()
}
}
class MideaFactory extends Factory{
createTV(){
return new MideaTV()
}
createFrige(){
return new MideaFrige()
}
}
// Client
function calculator(){
// 如果更换厂家只需要将MideaFactory替换掉即可
const factory: Factory = new MideaFactory();
return factory.createTV().getPrice()*10
+ factory.createFrige().getPrice*6
}
这里,冰箱和电视机构成了一个产品族,同一个品牌的电视机构成了一个产品级;我们需要把产品族(电视机,冰箱)放到一个工厂(海尔,美的工厂)里生产,让工厂生产产品族里的每一种产品。
分析到这里,可以得出,抽象工厂模式的优点主要有以下两点:
- 当出现产品族时,不需要建立那么多工厂了,让一个工厂生产整个产品族的产品,大大减少工厂的数量;
- 当需要新增一个产品族时,只需要新建相关的具体工厂,其余的代码都不用修改。
你可能早就有了疑问,如果说有一天我不想增加一个产品族(品牌)而是想增加一个产品级(空调),那我岂不是要修改所有的工厂(每个品牌都要建立空调厂)?
确实没错,那是因为开闭原则具有倾向性,在我们确定采用这种模式之前,就要考虑好要采购的所有家电类型,确定我们一般都只切换品牌,而不是更改家电种类。
这里就引出了抽象工厂模式的缺点:
增加产品族简单,但是增加产品级困难。
所以,只有在以下条件满足时才适用抽象工厂模式:
- 出现了产品级和产品族,且不能同时使用多个产品族。比如皮肤,我们不能在同一时间既用春季皮肤,又用夏季皮肤;比如采购品牌家电,我们不能既采购美的的又采购海尔的。
- 一般只切换产品族,不会轻易增加或者减少产品族里的产品。比如我们在做界面皮肤的时候,就考虑好了所有的界面元素,一般都只会切换皮肤,而不是随意增加或者减少界面元素。
总结
以上就是关于工厂三兄弟的全部内容了,总的来说三者各有优劣,主要看所遇到的场景是具有什么特点,能和哪个兄弟匹配上,就让哪个兄弟上。
目前我的工作中遇到的场景都比较简单,所以使用简单工厂模式就足够了,等将来遇到更加复杂的场景,我再来补充。
网友评论