廠方法模式
工廠方法模式是類的創(chuàng)建模式。工廠方法模式的用意是定義一個創(chuàng)建產(chǎn)品對象的工廠接口,將實(shí)際創(chuàng)建工廠推遲到子類中。
工廠方法模式
工廠方法模式是對簡單工廠模式進(jìn)一步抽象的結(jié)果。
假如是不使用反射的工廠方法模式,那么所有的if...
else if...else都放在工廠類中,勢必造成工廠類的無限臃腫
這時候就需要工廠方法模式來處理這個問題了。工廠方法模式中,核心的工廠類不再負(fù)責(zé)所有對象的創(chuàng)建,而是將具體的創(chuàng)建工作交給子類去做。這個類則搖身一變變成了一個抽象工廠角色,僅僅負(fù)責(zé)給出具體工廠子類必須實(shí)現(xiàn)的接口。
這一步的改進(jìn),使得系統(tǒng)可以在不修改具體工廠角色的情況下引入新的產(chǎn)品。
工廠方法模式結(jié)構(gòu)
使用工廠方法模式的系統(tǒng)涉及到以下角色:
1、抽象工廠角色
擔(dān)任這個角色的是工廠方法模式的核心,任何在模式中創(chuàng)建對象的工廠類必須實(shí)現(xiàn)這個接口
2、具體工廠角色
擔(dān)任這個角色的是實(shí)現(xiàn)了工廠接口的具體Java類,具體工廠角色與業(yè)務(wù)密切相關(guān),并且隨著使用者的調(diào)用以創(chuàng)建導(dǎo)出類
3、抽象導(dǎo)出角色
工廠方法模式所創(chuàng)建的對象的超類
4、具體導(dǎo)出角色
這個角色實(shí)現(xiàn)了抽象導(dǎo)出角色所聲明的接口,工廠方法模式所創(chuàng)建的每一個對象都是某個具體角色導(dǎo)出角色的實(shí)例
工廠方法模式的示例
有一個抽象工廠,導(dǎo)出文件的工廠:
它有兩個實(shí)現(xiàn)類,分別是導(dǎo)出HTML文件的工廠和導(dǎo)出PDF文件的工廠:
抽象產(chǎn)品角色,一個導(dǎo)出文件:
具體產(chǎn)品角色:
模擬客戶端調(diào)用,實(shí)例化出一個具體的工廠角色,根據(jù)傳入的參數(shù)返回不同的產(chǎn)品角色:
返回結(jié)果為:
導(dǎo)出財(cái)務(wù)版HTML文件
當(dāng)然,也可以修改代碼,看自己喜好,實(shí)例化出不同的子類并且調(diào)用export方法打印。
工廠方法模式在Java中的應(yīng)用及解讀
拿ThreadFactory舉個例子,顧名思義,這是一個生產(chǎn)線程的接口:
具體的線程工廠可以implements這個接口并實(shí)現(xiàn)newThread(Runnable r)方法,來生產(chǎn)具體線程工廠想要生產(chǎn)的線程。JDK在Executors給開發(fā)者提供了一個靜態(tài)內(nèi)部類DefaultThreadFactory,當(dāng)然開發(fā)者也可以自行實(shí)現(xiàn)這個接口,寫自定義的線程工廠。
總結(jié)
對于系統(tǒng)的設(shè)計(jì)應(yīng)該足夠靈活并盡可能降低代碼之間的耦合度,當(dāng)修改或增加一個新的功能時,使得使用者盡可能修改少的地方即可。假如設(shè)計(jì)不夠靈活,將無法面對多變的需求,可能一個極小的需求變更,都會使代碼結(jié)構(gòu)發(fā)生改變,并導(dǎo)致其他使用的人都要修改他們的代碼。牽一發(fā)而動全身,系統(tǒng)日后的維護(hù)將變得艱難。
ava設(shè)計(jì)模式
本系列文章共23篇,詳細(xì)介紹GOF (Gang Of Four)所定義的23種設(shè)計(jì)模式。共分為三大類對應(yīng)標(biāo)題中的3大招,每類中的每一種設(shè)計(jì)模式對應(yīng)3大招中的某一式:
第1招-創(chuàng)建型(共5式):單例模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式;
第2招-行為型(共11式):觀察者模式,策略模式,命令模式,狀態(tài)模式,解釋器模式,迭代器模式,中介者模式,訪問者模式,備忘錄模式,責(zé)任鏈模式,模板方法模式;
第3招-結(jié)構(gòu)型(共7式):適配器模式,外觀模式,代理模式,裝飾者模式,組合模式,橋接模式,享元模式。
UML (Unified Modeling Language, 統(tǒng)一建模語言):9大圖——構(gòu)件圖、類圖、對象圖、序列圖、協(xié)作圖、活動圖、狀態(tài)機(jī)圖、用例圖、部署圖;6關(guān)系——泛化=實(shí)現(xiàn)>組合>聚合>關(guān)聯(lián)>依賴。
OO(Object-Oriented, 面向?qū)ο?:面向?qū)ο蟮某绦蛟O(shè)計(jì)方法將數(shù)據(jù)及對數(shù)據(jù)的操作封裝在一起形成一個相互依賴不可分離的整體,即對象;對同類型的對象抽象出其共性形成類。
定義
應(yīng)用場景
優(yōu)缺點(diǎn)
Java代碼實(shí)例
小結(jié)
工廠方法模式:對象的工廠,將類的實(shí)例化過程延遲到子類進(jìn)行。
說到工廠模式就不得不提一下工廠模式三姐妹,簡單工廠模式、工廠方法模式和抽象工廠模式。其中簡單工廠模式存在諸多違背設(shè)計(jì)原則的問題,未被列入23種設(shè)計(jì)模式。而工廠方法模式解決了這些問題,特別是“開放關(guān)閉原則”,提供了工廠類的抽象,實(shí)現(xiàn)了可擴(kuò)展性。抽象工廠模式不是本文的重點(diǎn),留到下節(jié)介紹。
工廠方法模式涉及到四個角色:具體產(chǎn)品、抽象產(chǎn)品、具體工廠、抽象工廠。
工廠方法模式UML圖
產(chǎn)品種類不確定,后期可能添加新的產(chǎn)品時;
當(dāng)前類并不知道需要創(chuàng)建的是什么子類時;
不想在父類進(jìn)行實(shí)例化工作,想讓子類決定時。
優(yōu)點(diǎn):
良好的擴(kuò)展性,當(dāng)需要添加新的產(chǎn)品時無需修改原來的類,只需添加一個新的產(chǎn)品類和一個工廠類即可;
代碼結(jié)構(gòu)清晰,產(chǎn)品類是產(chǎn)品類,工廠負(fù)責(zé)創(chuàng)建,客戶端無需知道各種細(xì)節(jié),解耦了產(chǎn)品的創(chuàng)建過程;
增加了維護(hù)性,將創(chuàng)建過程放到了工廠類里,某一產(chǎn)品的創(chuàng)建發(fā)生變化時不會影響產(chǎn)品本身;
缺點(diǎn):
類的個數(shù)爆發(fā)式增長,每添加一種產(chǎn)品就需要添加兩個類,如果產(chǎn)品過多則導(dǎo)致類過多,增加類定義的內(nèi)存開銷;
在實(shí)現(xiàn)工廠方法模式這前我們先來看一下簡單工廠模式:
1.創(chuàng)建抽象產(chǎn)品IPhone;
2.創(chuàng)建具體產(chǎn)品MiPhone和ApplePhone;
抽象產(chǎn)品和具體產(chǎn)品
3.創(chuàng)建簡單工廠PhoneFactory;
4.使用工廠類進(jìn)行創(chuàng)建產(chǎn)品;
簡單工廠類及客戶端類
實(shí)現(xiàn)工廠方法模式需要四步:
1.定義產(chǎn)品接口;
2.添加具體產(chǎn)品實(shí)現(xiàn);
抽象產(chǎn)品及具體產(chǎn)品
3.定義創(chuàng)建工廠接口;
4.添加具體工廠實(shí)現(xiàn);
抽象工廠和具體工廠
5.客戶端使用。
客戶端使用
工廠模式三姐妹中最常用的是簡單工廠模式,但簡單工廠模式違背了設(shè)計(jì)原則中的開放關(guān)閉原則。而工廠方法模式解決了此問題,并且完美使用了里氏替換原則和依賴倒置原則。對于可能出現(xiàn)多種類別的實(shí)例實(shí)現(xiàn)時,建議使用工廠方法模式進(jìn)行解耦。
工廠模式(Factory Pattern),根據(jù)不同的名稱輸入返回不同類的實(shí)例,一般用來創(chuàng)建同一類對象。工廠模式的主要思想是將對象的創(chuàng)建與對象的實(shí)現(xiàn)分離。
在類似場景中,這些例子有以下特點(diǎn):
工廠模式分為簡單工廠模式、工廠方法模式、抽象工廠模式。
簡單工廠模式是由一個工廠對象決定創(chuàng)建出哪一種產(chǎn)品類的實(shí)例
簡單工廠模式包含如下角色:
類圖: AmericanoCoffee、LatteCoffee和CappuccinoCoffee都是繼承Coffee
代碼
abstract class Coffee {
constructor(public name: string) {
}
}
class AmericanoCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class LatteCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class CappuccinoCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class Café {
static order(name: string) {
switch (name) {
case 'Americano':
return new AmericanoCoffee('美式咖啡');
case 'Latte':
return new LatteCoffee('拿鐵咖啡');
case 'Cappuccino':
return new LatteCoffee('卡布奇諾');
default:
return null;
}
}
}
console.log(Café.order('Americano'));
console.log(Café.order('Latte'));
console.log(Café.order('Cappuccino'));
jQuery
class jQuery{
constructor(selector){
let elements = Array.from(document.querySelectorAll(selector));
let length = elements?elements.length:0;
for(let i=0;i<length;i++){
this[i]=elements[i];
}
this.length = length;
}
html(html){
if(html){
this[0].innerHTML=html;
}else{
return this[0].innerHTML;
}
}
}
window.$ = function(selector){
return new jQuery(selector);
}
和原生的 document.createElement 類似,Vue 和 React 這種具有虛擬 DOM 樹(Virtual Dom Tree)機(jī)制的框架在生成虛擬 DOM 的時候,都提供了 createElement 方法用來生成 VNode,用來作為真實(shí) DOM 節(jié)點(diǎn)的映射
// Vue
createElement('h3', { class: 'main-title' }, [
createElement('img', { class: 'avatar', attrs: { src: '../avatar.jpg' } }),
createElement('p', { class: 'user-desc' }, '放棄不難,但堅(jiān)持一定很酷')
])
// React
React.createElement('h3', { className: 'user-info' },
React.createElement('img', { src: '../avatar.jpg', className: 'avatar' }),
React.createElement('p', { className: 'user-desc' }, '放棄不難,但堅(jiān)持一定很酷')
)
createElement 函數(shù)結(jié)構(gòu)大概如下:
class Vnode (tag, data, children) { ... }
function createElement(tag, data, children) {
return new Vnode(tag, data, children)
}
可以看到 createElement 函數(shù)內(nèi)會進(jìn)行 VNode 的具體創(chuàng)建,創(chuàng)建的過程是很復(fù)雜的,而框架提供的 createElement 工廠方法封裝了復(fù)雜的創(chuàng)建與驗(yàn)證過程,對于使用者來說就很方便了。
工廠模式在源碼中應(yīng)用頻繁,以 vue-router 中的源碼為例,代碼位置:vue-router/src/index.js
// src/index.js
export default class VueRouter {
constructor(options) {
this.mode = mode // 路由模式
switch (mode) { // 簡單工廠
case 'history': // history 方式
this.history = new HTML5History(this, options.base)
break
case 'hash': // hash 方式
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract': // abstract 方式
this.history = new AbstractHistory(this, options.base)
break
default:
// ... 初始化失敗報(bào)錯
}
}
}
稍微解釋一下這里的源碼。mode 是路由創(chuàng)建的模式,這里有三種 History、Hash、Abstract,前兩種我們已經(jīng)很熟悉了,History 是 H5 的路由方式,Hash 是路由中帶 # 的路由方式,Abstract 代表非瀏覽器環(huán)境中路由方式,比如 Node、weex 等;this.history 用來保存路由實(shí)例,vue-router 中使用了工廠模式的思想來獲得響應(yīng)路由控制類的實(shí)例。
源碼里沒有把工廠方法的產(chǎn)品創(chuàng)建流程封裝出來,而是直接將產(chǎn)品實(shí)例的創(chuàng)建流程暴露在 VueRouter 的構(gòu)造函數(shù)中,在被 new 的時候創(chuàng)建對應(yīng)產(chǎn)品實(shí)例,相當(dāng)于 VueRouter的構(gòu)造函數(shù)就是一個工廠方法。
如果一個系統(tǒng)不是 SPA (Single Page Application,單頁應(yīng)用),而是是 MPA(Multi Page Application,多頁應(yīng)用),那么就需要創(chuàng)建多個 VueRouter 的實(shí)例,此時 VueRouter 的構(gòu)造函數(shù)也就是工廠方法將會被多次執(zhí)行,以分別獲得不同實(shí)例。
簡單工廠模式是根據(jù)輸入的不同返回不同產(chǎn)品的實(shí)例;而工廠方式的主要思想是增加抽象工廠類,將不同產(chǎn)品的創(chuàng)建分離到不同的工廠中,工廠類的職責(zé)比較單一。
abstract class Coffee {
constructor(public name: string) {
}
}
abstract class Factory {
abstract createCoffee(): Coffee;
}
class AmericanoCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class AmericanoCoffeeFactory extends Factory {
createCoffee() {
return new AmericanoCoffee('美式咖啡')
}
}
class LatteCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class LatteCoffeeFactory extends Factory {
createCoffee() {
return new LatteCoffee('拿鐵咖啡')
}
}
class CappuccinoCoffee extends Coffee {
constructor(public name: string) {
super(name);
}
}
class CappuccinoFactory extends Factory {
createCoffee() {
return new CappuccinoCoffee('卡布奇諾')
}
}
class Café {
static order(name: string) {
switch (name) {
case 'Americano':
return new AmericanoCoffeeFactory().createCoffee();
case 'Latte':
return new LatteCoffeeFactory().createCoffee();
case 'Cappuccino':
return new CappuccinoFactory().createCoffee();
default:
return null;
}
}
}
console.log(Café.order('Americano'));
console.log(Café.order('Latte'));
console.log(Café.order('Cappuccino'));
const settings={
'Americano': AmericanoCoffeeFactory,
'Latte': LatteCoffeeFactory,
'Cappuccino': CappuccinoFactory
}
console.log(new settings('Americano').createCoffee());
console.log(new settings('Latte').createCoffee());
console.log(new settings('Cappuccino').createCoffee());
帶來了額外的系統(tǒng)復(fù)雜度,增加了抽象性。
那在什么時候使用工廠模式呢:
什么時候不該用工廠模式:
濫用只是增加了不必要的系統(tǒng)復(fù)雜度,過猶不及。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。