分類
這里工廠模式分為2類:簡單工廠 和 工廠方法,下一節(jié)會介紹第3類工廠模式:抽象工廠。
簡單工廠
定義
簡單工廠:定義一個類來創(chuàng)建其他類的實(shí)例,根據(jù)參數(shù)的不同返回不同類的實(shí)例,通常這些類擁有相同的父類。
例子
假設(shè)現(xiàn)在有 3 款車,Benz、Audi 和 BMW,他們都繼承自父類 Car,并且重寫了父類方法 drive:
class Car {
drive () {
console.log('Car drive');
}
}
class Benz extends Car {
drive () {
console.log(`Benz drive`)
}
}
class Audi extends Car {
drive () {
console.log(`Audi drive`)
}
}
class BMW extends Car {
drive () {
console.log(`BMW drive`)
}
}
我們定義了一個父類 Car,包含一個方法 drive,Benz、Audi 和 BMW 繼承自共同的父類 Car。
那么我們在實(shí)例化這 3 款車的時候,就需要如下調(diào)用:
let benz = new Benz();
let audi = new Audi();
let bmw = new BMW();
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
這種寫法就很繁瑣,這時候就用到我們的簡單工廠了,提供一個工廠類:
class SimpleFactory {
static getCar (type) {
switch (type) {
case 'benz':
return new Benz();
case 'audi':
return new Audi();
case 'bmw':
return new BMW();
}
}
}
簡單工廠類 SimpleFactory 提供一個靜態(tài)方法 getCar,我們再實(shí)例化 3 款車的時候,就變成下面這樣了:
let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
這么一看,使用簡單工廠后代碼行數(shù)反而變多了,這種寫法真的有優(yōu)勢嗎?
我們要知道,設(shè)計(jì)模式并不是為了減少代碼行數(shù)而出現(xiàn)的,它是為了使我們的代碼更好擴(kuò)展,更好維護(hù),更方便使用而出現(xiàn)的。那么使用簡單工廠后,有什么好處呢?
簡單工廠,用戶不需要知道具體產(chǎn)品的類名,只需要知道對應(yīng)的參數(shù)即可,對于一些復(fù)雜的類名,可以減少用戶的記憶量,同時用戶無需了解這些對象是如何創(chuàng)建及組織的,有利于整個軟件體系結(jié)構(gòu)的優(yōu)化。
所以,使用簡單工廠,是將類的實(shí)例化交給工廠函數(shù)去做,對外提供統(tǒng)一的方法。我們要養(yǎng)成一個習(xí)慣,在代碼中 new 是一個需要慎重考慮的操作,new 出現(xiàn)的次數(shù)越多,代碼的耦合性就越強(qiáng),可維護(hù)性就越差,簡單工廠,就是在上面做了一層抽象,將 new 的操作封裝了起來,向外提供靜態(tài)方法供用戶調(diào)用,這樣就將耦合集中到了工廠函數(shù)中,而不是暴露在代碼的各個位置。
缺陷
簡單工廠有它的好處,必然也有它的缺點(diǎn),比如下面這種情況:
我們又新增了一類車 Ferrai,就需要在簡單工廠類 SimpleFactory 中再新增一個 case,添加 Ferrari 的實(shí)例化過程。每新增一類車,就要修改簡單工廠類,這樣做其實(shí)違背了設(shè)計(jì)原則中的開閉原則:對擴(kuò)展開放,對修改封閉,同時如果每類車在實(shí)例化之前需要做一些處理邏輯的話,SimpleFactory 會變的越來越復(fù)雜。
所以簡單工廠適用于產(chǎn)品類比較少并且不會頻繁增加的情況,那么有什么方法能解決簡單工廠存在的問題呢?
工廠方法
工廠方法模式是簡單工廠的進(jìn)一步優(yōu)化,我們不再提供一個統(tǒng)一的工廠類來創(chuàng)建所有的對象,而是針對不同的對象提供不同的工廠,也就是說每個對象都有一個與之對應(yīng)的工廠。
定義
工廠方法模式又稱為工廠模式,它的實(shí)質(zhì)是“定義一個創(chuàng)建對象的接口,但讓實(shí)現(xiàn)這個接口的類來決定實(shí)例化哪個類,工廠方法讓類的實(shí)例化推遲到子類中執(zhí)行”。
例子
光看概念還是很難理解,我們沿著上面的簡單工廠來做修改,舉例說明。
還是先定義一個父類 Car,提供一個方法 drive:
class Car {
drive () {
console.log('Car drive');
}
}
每一款車都繼承自父類 Car,并重寫方法 drive:
class Benz extends Car {
drive () {
console.log(`Benz drive`)
}
}
class Audi extends Car {
drive () {
console.log(`Audi drive`)
}
}
class BMW extends Car {
drive () {
console.log(`BMW drive`)
}
}
然后按照定義,我們提供一個創(chuàng)建對象的接口:
class IFactory {
getCar () {
throw new Error('不允許直接調(diào)用抽象方法,請自己實(shí)現(xiàn)');
}
}
每款車都提供一個工廠類,實(shí)現(xiàn)自上述接口,因?yàn)?JavaScript 中沒有 implements,所以用 extends 代替:
class BenzFactory extends IFactory {
getCar () {
return new Benz();
}
}
class AudiFactory extends IFactory {
getCar () {
return new Audi();
}
}
class BMWFactory extends IFactory {
getCar () {
return new BMW();
}
}
這樣當(dāng)我們需要實(shí)例化每款車的時候,就按如下操作:
let benzFactory = new BenzFactory();
let benz = benzFactory.getCar();
let audiFactory = new AudiFactory();
let audi = audiFactory.getCar();
let bmwFactory = new BMWFactory();
let bmw = bmwFactory.getCar();
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
我們再來對比一下簡單工廠的實(shí)例化過程:
let benz = SimpleFactory.getCar('benz');
let audi = SimpleFactory.getCar('audi');
let bmw = SimpleFactory.getCar('bmw');
benz.drive(); // Benz drive
audi.drive(); // Audi drive
bmw.drive(); // BMW drive
我們用 UML 類圖來描述一下工廠方法模式:
可以看到,同樣是為了實(shí)例化一個對象,怎么就變得這么復(fù)雜了呢····我們來總結(jié)一下工廠方法模式的步驟:
- 定義產(chǎn)品父類 -- Car
- 定義子類實(shí)現(xiàn)父類,并重寫父類方法 -- BenzCar、AudiCar、BMWCar
- 定義抽象接口,以及抽象方法 -- IFactory
- 定義工廠類,實(shí)現(xiàn)自抽象接口,并且實(shí)現(xiàn)抽象方法 -- BenzFactory、AudiFactory、BMWFactory
- new 工廠類,調(diào)用方法進(jìn)行實(shí)例化
那么工廠方法增加了如此多的流程,提高了復(fù)雜度,究竟解決了簡單工廠的什么問題呢?
通過上文可以知道,簡單工廠在新增一款車的時候,需要修改簡單工廠類 SimpleFactory,違背了設(shè)計(jì)模式中的“開閉原則”:對擴(kuò)展開放,對修改封閉。
當(dāng)工廠方法需要新增一款車的時候,比如 Ferrari,只需要定義自己的產(chǎn)品類 Ferrari 以及自己的工廠類 FerrariFactory:
class Ferrari extends Car {
drive () {
console.log(`Ferrari drive`)
}
}
class FerrariFactory extends IFactory {
getCar () {
return new Ferrari();
}
}
let ferrariFactory = new FerrariFactory();
let ferrari = ferrariFactory.getCar();
ferrari.drive(); // Ferrari drive
完全不用修改已有的抽象接口 IFactory,只需要擴(kuò)展實(shí)現(xiàn)自己需要的就可以了,不會影響已有代碼。這就是對擴(kuò)展開放,對修改封閉。
總結(jié)
-
簡單工廠模式
- 解決了用戶多次自己實(shí)例化的問題,屏蔽細(xì)節(jié),提供統(tǒng)一工廠,將實(shí)例化的過程封裝到內(nèi)部,提供給用戶統(tǒng)一的方法,只需要傳遞不同的參數(shù)就可以完成實(shí)例化過程,有利于軟件結(jié)構(gòu)體系的優(yōu)化;
- 但不足之處是,增加新的子類時,需要修改工廠類,違背了“開閉原則”,并且工廠類會變得越來越臃腫;
- 簡單工廠模式適用于固定的,不會頻繁新增子類的使用場景
-
工廠方法模式
- 通過在上層再增加一層抽象,提供了接口,每個子類都有自己的工廠類,工廠類實(shí)現(xiàn)自接口,并且實(shí)現(xiàn)了統(tǒng)一的抽象方法,這樣在新增子類的時候,完全不需要修改接口,只需要新增自己的產(chǎn)品類和工廠類就可以了,符合“開閉原則”;
- 但不足之處也正是如此,持續(xù)的新增子類,導(dǎo)致系統(tǒng)類的個數(shù)將成對增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,同時有更多的類需要編譯和運(yùn)行,會給系統(tǒng)代理一些額外的開銷;
- 工廠方法模式適用于會頻繁新增子類的復(fù)雜場景;
|