工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。工廠模式根據抽象程度的不一樣能夠分爲:簡單工廠
,工廠方法
和抽象工廠
。javascript
若是隻接觸過JavaScript這門語言的的人可能會對抽象這個詞的概念有點模糊,由於JavaScript一直將abstract
做爲保留字而沒有去實現它。若是不能很好的理解抽象的概念,那麼就很難理解工廠模式中的三種方法的異同。因此,咱們先以一個場景去簡單的講述一下抽象和工廠的概念。前端
想象一下你的女友生日要到了,你想知道她想要什麼,因而你問她:「親愛的,生日要到了你想要什麼生日禮物啊?」 正巧你女友是個貓奴,最經迷上了抖音上的一隻超級可愛的蘇格蘭摺耳貓,她也很想要一隻網紅同款貓。vue
因而她回答你說:「親愛的,我想要一隻動物。」java
你心平氣和的問她:「想要什麼動物啊?」程序員
你女朋友說:「我想要貓科動物。」vue-router
這時你心裏就納悶了,貓科動物有老虎,獅子,豹子,猞猁,還有各類小貓,我哪裏知道你要什麼?segmentfault
因而你問女朋友:「你要哪一種貓科動物啊?」設計模式
「笨死了,還要哪一種,確定是小貓咪啊,難道咱們家還能像迪拜土豪那樣養老虎啊!」你女友答道。promise
「好好, 那你想要哪一個品種的貓呢?」你問道瀏覽器
「我想要外國的品種, 不要中國的土貓」 你女朋友傲嬌的回答到。
這時你已經快奔潰了,做爲程序員的你再也受不了這種擠牙膏式的提問,因而你哀求到:「親愛的,你就直接告訴我你到底想要哪一個品種,哪一個顏色,多大的貓?」
你女朋友想了想抖音的那隻貓,回答道:「我想要一隻
灰色
的,不超過1
歲的蘇格蘭短耳貓
!」
因而,你在女朋友生日當天到全國最大的寵物批發市場裏面去,挑了一隻「灰色的,不超過1歲的蘇格蘭短耳貓」回家送給了你女朋友, 圓了你女朋友擁有網紅同款貓的夢想!
上面中你最終買到並送給女朋友那隻貓能夠被看做是一個實例對象,寵物批發市場能夠看做是一個工廠,咱們能夠認爲它是一個函數,這個工廠函數裏面有着各類各樣的動物,那麼你是如何獲取到實例的呢?由於你給寵物批發市場傳遞了正確的參數: 「color: 灰色」,「age: 不超過1歲」,"breed:蘇格蘭短耳",「category: 貓"
。前面的對話中, 你女友回答「動物」,「貓科動物」,「國外的品種」讓你不明白她到底想要什麼,就是由於她說得太抽象了。
她回答的是一大類動物的共有特徵而不是具體動物,這種將復瑣事物的一個或多個共有特徵抽取出來的思惟過程就是抽象。
既然已經明白了抽象的概念,下面咱們來看一下以前提到的工廠模式的三種實現方法: 簡單工廠模式
、工廠方法模式
、抽象工廠模式
。
簡單工廠模式
又叫靜態工廠模式
,由一個工廠對象決定建立某一種產品對象類的實例。主要用來建立同一類對象。
在實際的項目中,咱們經常須要根據用戶的權限來渲染不一樣的頁面,高級權限的用戶所擁有的頁面有些是沒法被低級權限的用戶所查看。因此咱們能夠在不一樣權限等級用戶的構造函數中,保存該用戶可以看到的頁面。在根據權限實例化用戶。使用ES6重寫簡單工廠模式時,咱們再也不使用構造函數建立對象,而是使用class的新語法,並使用static關鍵字將簡單工廠封裝到User
類的靜態方法中.代碼以下:
//User類
class User {
//構造器
constructor(opt) {
this.name = opt.name;
this.viewPage = opt.viewPage;
}
//靜態方法
static getInstance(role) {
switch (role) {
case 'superAdmin':
return new User({ name: '超級管理員', viewPage: ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] });
break;
case 'admin':
return new User({ name: '管理員', viewPage: ['首頁', '通信錄', '發現頁', '應用數據'] });
break;
case 'user':
return new User({ name: '普通用戶', viewPage: ['首頁', '通信錄', '發現頁'] });
break;
default:
throw new Error('參數錯誤, 可選參數:superAdmin、admin、user')
}
}
}
//調用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');
複製代碼
User
就是一個簡單工廠,在該函數中有3個實例中分別對應不一樣的權限的用戶。當咱們調用工廠函數時,只須要傳遞superAdmin
, admin
, user
這三個可選參數中的一個獲取對應的實例對象。
簡單工廠的優勢在於,你只須要一個正確的參數,就能夠獲取到你所須要的對象,而無需知道其建立的具體細節。可是在函數內包含了全部對象的建立邏輯(構造函數)和判斷邏輯的代碼,每增長新的構造函數還須要修改判斷邏輯代碼。當咱們的對象不是上面的3個而是30個或更多時,這個函數會成爲一個龐大的超級函數,便得難以維護。因此,簡單工廠只能做用於建立的對象數量較少,對象的建立邏輯不復雜時使用。
工廠方法模式的本意是將實際建立對象的工做推遲到子類中,這樣核心類就變成了抽象類。可是在JavaScript中很難像傳統面向對象那樣去實現建立抽象類。因此在JavaScript中咱們只須要參考它的核心思想便可。咱們能夠將工廠方法看做是一個實例化對象的工廠類。雖然ES6也沒有實現abstract
,可是咱們可使用new.target
來模擬出抽象類。new.target
指向直接被new
執行的構造函數,咱們對new.target
進行判斷,若是指向了該類則拋出錯誤來使得該類成爲抽象類。
在簡單工廠模式中,咱們每添加一個構造函數須要修改兩處代碼。如今咱們使用工廠方法模式改造上面的代碼,剛纔提到,工廠方法咱們只把它看做是一個實例化對象的工廠,它只作實例化對象這一件事情!
class User {
constructor(name = '', viewPage = []) {
if(new.target === User) {
throw new Error('抽象類不能實例化!');
}
this.name = name;
this.viewPage = viewPage;
}
}
class UserFactory extends User {
constructor(name, viewPage) {
super(name, viewPage)
}
create(role) {
switch (role) {
case 'superAdmin':
return new UserFactory( '超級管理員', ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] );
break;
case 'admin':
return new UserFactory( '普通用戶', ['首頁', '通信錄', '發現頁'] );
break;
case 'user':
return new UserFactory( '普通用戶', ['首頁', '通信錄', '發現頁'] );
break;
default:
throw new Error('參數錯誤, 可選參數:superAdmin、admin、user')
}
}
}
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('user');
複製代碼
上面介紹了簡單工廠模式和工廠方法模式都是直接生成實例,可是抽象工廠模式不一樣,抽象工廠模式並不直接生成實例, 而是用於對產品類簇
的建立。
上面例子中的superAdmin
,admin
,user
三種用戶角色,其中user多是使用不一樣的社交媒體帳戶進行註冊的,例如:wechat,qq,weibo
。那麼這三類社交媒體帳戶就是對應的類簇。在抽象工廠中,類簇通常用父類定義,並在父類中定義一些抽象方法,再經過抽象工廠讓子類繼承父類。因此,抽象工廠實際上是實現子類繼承父類的方法。
上面提到的抽象方法是指聲明但不能使用的方法。在其餘傳統面向對象的語言中經常使用abstract
進行聲明,可是在JavaScript中,abstract
是屬於保留字,可是咱們能夠經過在類的方法中拋出錯誤來模擬抽象類。
function getAbstractUserFactory(type) {
switch (type) {
case 'wechat':
return UserOfWechat;
break;
case 'qq':
return UserOfQq;
break;
case 'weibo':
return UserOfWeibo;
break;
default:
throw new Error('參數錯誤, 可選參數:superAdmin、admin、user')
}
}
let WechatUserClass = getAbstractUserFactory('wechat');
let QqUserClass = getAbstractUserFactory('qq');
let WeiboUserClass = getAbstractUserFactory('weibo');
let wechatUser = new WechatUserClass('微信小李');
let qqUser = new QqUserClass('QQ小李');
let weiboUser = new WeiboUserClass('微博小李');
複製代碼
在實際的前端業務中,最經常使用的簡單工廠模式。若是不是超大型的項目,是很難有機會使用到工廠方法模式和抽象工廠方法模式的。下面我介紹在Vue項目中實際使用到的簡單工廠模式
的應用。
在普通的vue + vue-router
的項目中,咱們一般將全部的路由寫入到router/index.js
這個文件中。下面的代碼我相信vue的開發者會很是熟悉,總共有5個頁面的路由:
// index.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'
import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'
Vue.use(Router)
export default new Router({
routes: [
//重定向到登陸頁
{
path: '/',
redirect: '/login'
},
//登錄頁
{
path: '/login',
name: 'Login',
component: Login
},
//超級管理員頁面
{
path: '/super-admin',
name: 'SuperAdmin',
component: SuperAdmin
},
//普通管理員頁面
{
path: '/normal-admin',
name: 'NormalAdmin',
component: NormalAdmin
},
//普通用戶頁面
{
path: '/user',
name: 'User',
component: User
},
//404頁面
{
path: '*',
name: 'NotFound404',
component: NotFound404
}
]
})
複製代碼
當涉及權限管理頁面的時候,一般須要在用戶登錄根據權限開放固定的訪問頁面並進行相應權限的頁面跳轉。可是若是咱們仍是按照老辦法將全部的路由寫入到router/index.js
這個文件中,那麼低權限的用戶若是知道高權限路由時,能夠經過在瀏覽器上輸入url跳轉到高權限的頁面。因此咱們必須在登錄的時候根據權限使用vue-router
提供的addRoutes
方法給予用戶相對應的路由權限。這個時候就可使用簡單工廠方法來改造上面的代碼。
在router/index.js
文件中,咱們只提供/login
這一個路由頁面。
//index.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'
Vue.use(Router)
export default new Router({
routes: [
//重定向到登陸頁
{
path: '/',
redirect: '/login'
},
//登錄頁
{
path: '/login',
name: 'Login',
component: Login
}
]
})
複製代碼
咱們在router/
文件夾下新建一個routerFactory.js
文件,導出routerFactory
簡單工廠函數,用於根據用戶權限提供路由權限,代碼以下
//routerFactory.js
import SuperAdmin from '../components/SuperAdmin.vue'
import NormalAdmin from '../components/Admin.vue'
import User from '../components/User.vue'
import NotFound404 from '../components/404.vue'
let AllRoute = [
//超級管理員頁面
{
path: '/super-admin',
name: 'SuperAdmin',
component: SuperAdmin
},
//普通管理員頁面
{
path: '/normal-admin',
name: 'NormalAdmin',
component: NormalAdmin
},
//普通用戶頁面
{
path: '/user',
name: 'User',
component: User
},
//404頁面
{
path: '*',
name: 'NotFound404',
component: NotFound404
}
]
let routerFactory = (role) => {
switch (role) {
case 'superAdmin':
return {
name: 'SuperAdmin',
route: AllRoute
};
break;
case 'normalAdmin':
return {
name: 'NormalAdmin',
route: AllRoute.splice(1)
}
break;
case 'user':
return {
name: 'User',
route: AllRoute.splice(2)
}
break;
default:
throw new Error('參數錯誤! 可選參數: superAdmin, normalAdmin, user')
}
}
export { routerFactory }
複製代碼
在登錄頁導入該方法,請求登錄接口後根據權限添加路由:
//Login.vue
import {routerFactory} from '../router/routerFactory.js'
export default {
//...
methods: {
userLogin() {
//請求登錄接口, 獲取用戶權限, 根據權限調用this.getRoute方法
//..
},
getRoute(role) {
//根據權限調用routerFactory方法
let routerObj = routerFactory(role);
//給vue-router添加該權限所擁有的路由頁面
this.$router.addRoutes(routerObj.route);
//跳轉到相應頁面
this.$router.push({name: routerObj.name})
}
}
};
複製代碼
在實際項目中,由於使用this.$router.addRoutes
方法添加的路由刷新後不能保存,因此會致使路由沒法訪問。一般的作法是本地加密保存用戶信息,在刷新後獲取本地權限並解密,根據權限從新添加路由。這裏由於和工廠模式沒有太大的關係就再也不贅述。
上面說到的三種工廠模式和單例模式同樣,都是屬於建立型的設計模式。簡單工廠模式又叫靜態工廠方法,用來建立某一種產品對象的實例,用來建立單一對象;工廠方法模式是將建立實例推遲到子類中進行;抽象工廠模式是對類的工廠抽象用來建立產品類簇,不負責建立某一類產品的實例。在實際的業務中,須要根據實際的業務複雜度來選擇合適的模式。對於非大型的前端應用來講,靈活使用簡單工廠其實就能解決大部分問題。
將new操做簡單封裝,遇到new的時候就應該考慮是否用工廠模式;
舉個例子:
你去購買漢堡,直接點餐、取餐、不會本身親自作;(買者不關注漢堡是怎麼作的)
商店要封裝
作漢堡的工做,作好直接給買者;(商家也不會告訴你是怎麼作的,也不會傻到給你一片面包,一些奶油,一些生菜讓你本身作)
外部不準關心內部構造器是怎麼生成的,只需調用一個工廠方法生成一個實例便可;
構造函數和建立者分離,符合開放封閉原則
$(selector)
jQuery中$('div')
和new $('div')
哪一個好用,很顯然直接$()
最方便 ,這是由於$()
已是一個工廠方法了;class jQuery {
constructor(selector) {
super(selector)
}
// ....
}
window.$ = function(selector) {
return new jQuery(selector)
}
複製代碼
createElement()
React.createElement()
方法就是一個工廠方法
經過promise
的方式resolve
出來一個組件
參考:https://segmentfault.com/a/1190000014196851