今天要来聊聊 Creational Patten 当中的工厂模式。
当我们透过类别建立出实例的时候,其实感觉就像是一个工厂生产出了产品。而同一个工厂 (类别),可以生产出无限多个同样者产品 (实例)。
回到我们先前的例子,这里的 BaseballPlayer
和 TennisPlayer
类别继承了 Athlete
,并能够各自产出各自的实例,并且针对 hit
方法有不同的实作方式。
abstract class Athlete {
constructor() {}
abstract hit(): void;
}
class BaseballPlayer extends Athlete {
hit() {
console.log('Baseball player can hit baseball')
}
}
class TennisPlayer extends Athlete {
hit() {
console.log('Tennis player can hit tennis')
}
}
const jeter = new BaseballPlayer()
const federer = new TennisPlayer()
jeter.hit() // Baseball player can hit baseball
federer.hit() // Tennis player can hit tennis
但如果我们希望能够有个 AthleteFactory
,像是中央生产工厂,不管我想要 baseball player 还是 tennis player,找它就可以生产了
於是,这里就出现了一个简单的解法:建立一个静态类别AthleteFactory
,并且有一个静态方法 trainAthlete
,可以根据使用者的输入,来判断要提供什麽样的产品
class AthleteFactory {
static trainAthlete(category: string): Athlete {
switch (category) {
case 'baseball':
return new BaseballPlayer()
case 'tennis':
return new TennisPlayer()
default:
return null
}
}
}
所以如果我想要一位 baseball player,就在呼叫方法的时候,输入 'baseball'。想要一位 tennis player 的时候,就输入 'tennis',就能得到期待中的结果。
const ohtani = AthleteFactory.trainAthlete('baseball')
const nadal = AthleteFactory.trainAthlete('tennis')
ohtani.hit() // Baseball player can hit baseball
nadal.hit() // Tennis player can hit tennis
另一方面,如果想要持续增加不同的产品,只要在 AthleteFactory
当中持续扩充 switch 区块就行!
使用者能够快速从工厂取出需要的实例来使用,不需要自己去找目标类别来建立实例。另一方面,使用者也不需要管这些实例是怎麽被建立的,只要负责使用就行。
简单工厂的实作方式简单好懂,能够快速上手(平常可能不知不觉就采用了简单工厂模式)。
不过缺点就是,如果要新增一个新的商品,就必须要回去修改 AthleteFactory
当中的程序码,有可能会影响到旧有的程序码。另一方面,简单工厂集中了所有建立实例的逻辑与责任,一旦不能正常运作,那麽所有产品都会受到影响。
所以,与其让某个实际的工厂拥有所有生产产品的逻辑,不如就让每个产品有各自的工厂,但却有遵守相同的生产方式。
所以在工厂模式中,没有一个中央生产的工厂,只有一个指导其他工厂如何运作的抽象介面,像是下面的 AthleteFactory
interface AthleteFactory {
trainAthlete(): Athlete;
}
接着,我们分别位 baseball player 和 tennis player 建立 BaseballPlayerFactory
和 TennisPlayerFactory
工厂,并且执行 AthleteFactory
介面
class BaseballPlayerFactory implements AthleteFactory {
constructor() {}
trainAthlete() {
return new BaseballPlayer()
}
}
class TennisPlayerFactory implements AthleteFactory {
constructor() {}
trainAthlete() {
return new TennisPlayer()
}
}
最後,如果我们希望生产 baseball player,就建立一个 baseballPlayerFactory
;如果我们希望生产 tennis player,就建立一个 tennisPlayerFactory
const baseballPlayerFactory = new BaseballPlayerFactory()
const tennisPlayerFactory = new TennisPlayerFactory()
接着,两者可以用同样的方式,建立需要的实例,得到预期中的结果
const ohtani = baseballPlayerFactory.trainAthlete()
const nadal = tennisPlayerFactory.trainAthlete()
ohtani.hit() // Baseball player can hit baseball
nadal.hit() // Tennis player can hit tennis
有些时候我们因应外在环境的变动,需要产出类似的、但是又截然不同的实例(譬如棒球选手和网球选手),但又要避免像简单工厂那样,因集中生产所造成的维护和扩充问题,最後这里我们透过「抽象」的方式来解决问题。
首先是建立一个介面(或是类别也可以)来定义各个工厂的实作要求 (譬如 trainAthlete
方法),但是不定义详细的实作方式。
接着,让不同的产品建立相对应不同的工厂,并各自执行 trainAthlete
方法。譬如 BaseballPlayerFactory
的做法就是呼叫 new BaseballPlayer()
。
最後,使用者根据需求,呼叫需要的工厂来生产实例。
相对於简单工厂模式,工厂模式的好处是满足了「单一功能原则」,让 AthleteFactory
只管要有什麽方法,而不用管实作细节;也满足了「开放封闭原则」,未来有任何新的需求出现,只要按照介面(或类别)设计新的工程即可,大大提升了维护性和扩充性。
不过缺点就是,每当有新需求出现,我们就需要建立一个新的工厂 (e.g., xxxFactory
),以及负责生产实例的类别,两者会成双成对出现。
<<: Day 19 - WooCommerce: 初始化付款外挂
报名这次的铁人赛事十分紧张,对於我自己资讯方面的技术并不是那麽熟练,想利用铁人赛来督促自己学习新的...
简介 滚动式选单依然是常用基本元件之一,使用起来也非常容易。直接上图。 选项里面可以放Int或Str...
我想用录制我的Mac电脑的荧幕和声音,我该怎麽办? 其实,如果你使用合适的应用软件,你可以简单地将M...
阵列 当我会想储存比较复杂的资料的时後就会使用物件 除了物件,当这些资料有顺序的话,我也可以使用阵列...
在HTTP请求中 PUT 跟 PATCH 都代表更新 然後他们之间比较主要的差异在於 PUT 用在更...