摘要:簡單工廠模式簡單工廠模式又叫靜態工廠模式,由一個工廠對象決定創建某一種產品對象類的實例。工廠方法模式工廠方法模式的本意是將實際創建對象的工作推遲到子類中,這樣核心類就變成了抽象類。抽象工廠模式一般用在
1 什么是工廠模式?
工廠模式是用來創建對象的一種最常用的設計模式。我們不暴露創建對象的具體邏輯,而是將將邏輯封裝在一個函數中,那么這個函數就可以被視為一個工廠。工廠模式根據抽象程度的不同可以分為:簡單工廠,工廠方法和抽象工廠。
如果只接觸過JavaScript這門語言的的人可能會對抽象這個詞的概念有點模糊,因為JavaScript一直將abstract作為保留字而沒有去實現它。如果不能很好的理解抽象的概念,那么就很難理解工廠模式中的三種方法的異同。所以,我們先以一個場景去簡單的講述一下抽象和工廠的概念。
想象一下你的女朋友生日要到了,你想知道她想要什么,于是你問她:“親愛的,生日要到了你想要什么生日禮物啊?”正巧你女朋友是個貓奴,最經迷上了抖音上的一只超級可愛的蘇格蘭折耳貓,她也很想要一只網紅同款貓。
于是她回答你說:“親愛的,我想要一只動物。”
你心平氣和的問她:“想要什么動物啊?”
你女友說:“我想要貓科動物。”
這時你內心就納悶了,貓科動物有老虎,獅子,豹子,猞猁,還有各種小貓,我哪里知道你要什么?
于是你問女友:“你要哪種貓科動物啊?”
“笨死了,還要哪種,肯定是小貓咪啊,難道我們家還能像迪拜土豪那樣養老虎啊!”你女朋友答道。
“好好, 那你想要哪個品種的貓呢?”你問道
“我想要外國的品種, 不要中國的土貓” 你女友傲嬌的回答到。
這時你已經快奔潰了,作為程序員的你再也受不了這種擠牙膏式的提問,于是你哀求到:“親愛的,你就直接告訴我你到底想要哪個品種,哪個顏色,多大的貓?”
你女友想了想抖音的那只貓,回答道:“我想要一只灰色的,不超過1歲的蘇格蘭短耳貓!”
于是,你在女友生日當天到全國最大的寵物批發市場里面去,挑了一只“灰色的,不超過1歲的蘇格蘭短耳貓”回家送給了你女友, 圓了你女友擁有網紅同款貓的夢想!
上面中你最終買到并送給女友那只貓可以被看作是一個實例對象,寵物批發市場可以看作是一個工廠,我們可以認為它是一個函數,這個工廠函數里面有著各種各樣的動物,那么你是如何獲取到實例的呢?因為你給寵物批發市場傳遞了正確的參數, “color: 灰色”,“age: 不超過1歲”,"breed:蘇格蘭短耳",**“category:
貓"。前面的對話中, 你女朋友回答“動物”,“貓科動物”,“國外的品種”讓你不明白她到底想要什么,就是因為她說得太抽象了。她回答的是一大類動物的共有特征而不是具體動物,這種將復雜事物的一個或多個共有特征抽取出來的思維過程就是抽象**。
既然已經明白了抽象的概念,下面我們來看一下之前提到的工廠模式的三種實現方法: 簡單工廠模式、工廠方法模式、抽象工廠模式。
1.1 簡單工廠模式簡單工廠模式又叫靜態工廠模式,由一個工廠對象決定創建某一種產品對象類的實例。主要用來創建同一類對象。
在實際的項目中,我們常常需要根據用戶的權限來渲染不同的頁面,高級權限的用戶所擁有的頁面有些是無法被低級權限的用戶所查看。所以我們可以在不同權限等級用戶的構造函數中,保存該用戶能夠看到的頁面。在根據權限實例化用戶。代碼如下:
let UserFactory = function (role) { function SuperAdmin() { this.name = "超級管理員", this.viewPage = ["首頁", "通訊錄", "發現頁", "應用數據", "權限管理"] } function Admin() { this.name = "管理員", this.viewPage = ["首頁", "通訊錄", "發現頁", "應用數據"] } function NormalUser() { this.name = "普通用戶", this.viewPage = ["首頁", "通訊錄", "發現頁"] } switch (role) { case "superAdmin": return new SuperAdmin(); break; case "admin": return new Admin(); break; case "user": return new NormalUser(); break; default: throw new Error("參數錯誤, 可選參數:superAdmin、admin、user"); } } //調用 let superAdmin = UserFactory("superAdmin"); let admin = UserFactory("admin") let normalUser = UserFactory("user")
UserFactory就是一個簡單工廠,在該函數中有3個構造函數分別對應不同的權限的用戶。當我們調用工廠函數時,只需要傳遞superAdmin, admin, user這三個可選參數中的一個獲取對應的實例對象。你也許發現,我們的這三類用戶的構造函數內部很相識,我們還可以對其進行優化。
let UserFactory = function (role) { function User(opt) { this.name = opt.name; this.viewPage = opt.viewPage; } 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 = UserFactory("superAdmin"); let admin = UserFactory("admin") let normalUser = UserFactory("user")
簡單工廠的優點在于,你只需要一個正確的參數,就可以獲取到你所需要的對象,而無需知道其創建的具體細節。但是在函數內包含了所有對象的創建邏輯(構造函數)和判斷邏輯的代碼,每增加新的構造函數還需要修改判斷邏輯代碼。當我們的對象不是上面的3個而是30個或更多時,這個函數會成為一個龐大的超級函數,便得難以維護。所以,簡單工廠只能作用于創建的對象數量較少,對象的創建邏輯不復雜時使用。
1.2 工廠方法模式工廠方法模式的本意是將實際創建對象的工作推遲到子類中,這樣核心類就變成了抽象類。但是在JavaScript中很難像傳統面向對象那樣去實現創建抽象類。所以在JavaScript中我們只需要參考它的核心思想即可。我們可以將工廠方法看作是一個實例化對象的工廠類。
在簡單工廠模式中,我們每添加一個構造函數需要修改兩處代碼。現在我們使用工廠方法模式改造上面的代碼,剛才提到,工廠方法我們只把它看作是一個實例化對象的工廠,它只做實例化對象這一件事情! 我們采用安全模式創建對象。
//安全模式創建的工廠方法函數 let UserFactory = function(role) { if(this instanceof UserFactory) { var s = new this[role](); return s; } else { return new UserFactory(role); } } //工廠方法函數的原型中設置所有對象的構造函數 UserFactory.prototype = { SuperAdmin: function() { this.name = "超級管理員", this.viewPage = ["首頁", "通訊錄", "發現頁", "應用數據", "權限管理"] }, Admin: function() { this.name = "管理員", this.viewPage = ["首頁", "通訊錄", "發現頁", "應用數據"] }, NormalUser: function() { this.name = "普通用戶", this.viewPage = ["首頁", "通訊錄", "發現頁"] } } //調用 let superAdmin = UserFactory("SuperAdmin"); let admin = UserFactory("Admin") let normalUser = UserFactory("NormalUser")
上面的這段代碼就很好的解決了每添加一個構造函數就需要修改兩處代碼的問題,如果我們需要添加新的角色,只需要在UserFactory.prototype中添加。例如,我們需要添加一個VipUser:
UserFactory.prototype = { //.... VipUser: function() { this.name = "付費用戶", this.viewPage = ["首頁", "通訊錄", "發現頁", "VIP頁"] } } //調用 let vipUser = UserFactory("VipUser");
上面的這段代碼中,使用到的安全模式可能很難一次就能理解。
let UserFactory = function(role) { if(this instanceof UserFactory) { var s = new this[role](); return s; } else { return new UserFactory(role); } }
因為我們將SuperAdmin、Admin、NormalUser等構造函數保存到了UserFactory.prototype中,也就意味著我們必須實例化UserFactory函數才能夠進行以上對象的實例化。如下面代碼所示
let UserFactory = function() {} UserFactory.prototype = { //... } //調用 let factory = new UserFactory(); let superAdmin = new factory.SuperAdmin();
在上面的調用函數的過程中, 一旦我們在任何階段忘記使用new, 那么就無法正確獲取到superAdmin這個對象。但是一旦使用安全模式去進行實例化,就能很好解決上面的問題。
1.3 抽象工廠模式上面介紹了簡單工廠模式和工廠方法模式都是直接生成實例,但是抽象工廠模式不同,抽象工廠模式并不直接生成實例, 而是用于對產品類簇的創建。
上面例子中的superAdmin,admin,user三種用戶角色,其中user可能是使用不同的社交媒體賬戶進行注冊的,例如:wechat,qq,weibo。那么這三類社交媒體賬戶就是對應的類簇。在抽象工廠中,類簇一般用父類定義,并在父類中定義一些抽象方法,再通過抽象工廠讓子類繼承父類。所以,抽象工廠其實是實現子類繼承父類的方法。
上面提到的抽象方法是指聲明但不能使用的方法。在其他傳統面向對象的語言中常用abstract進行聲明,但是在JavaScript中,abstract是屬于保留字,但是我們可以通過在類的方法中拋出錯誤來模擬抽象類。
let WechatUser = function() {} WechatUser.prototype = { getName: function() { return new Error("抽象方法不能調用"); } }
上述代碼中的getPrice就是抽象方法,我們定義它但是卻沒有去實現。如果子類繼承WechatUser但是并沒有去重寫getName,那么子類的實例化對象就會調用父類的getName方法并拋出錯誤提示。
下面我們分別來實現賬號管理的抽象工廠方法:
let AccountAbstractFactory = function(subType, superType) { //判斷抽象工廠中是否有該抽象類 if(typeof AccountAbstractFactory[superType] === "function") { //緩存類 function F() {}; //繼承父類屬性和方法 F.prototype = new AccountAbstractFactory[superType] (); //將子類的constructor指向子類 subType.constructor = subType; //子類原型繼承父類 subType.prototype = new F(); } else { throw new Error("抽象類不存在!") } } //微信用戶抽象類 AccountAbstractFactory.WechatUser = function() { this.type = "wechat"; } AccountAbstractFactory.WechatUser.prototype = { getName: function() { return new Error("抽象方法不能調用"); } } //qq用戶抽象類 AccountAbstractFactory.QqUser = function() { this.type = "qq"; } AccountAbstractFactory.QqUser.prototype = { getName: function() { return new Error("抽象方法不能調用"); } } //新浪微博用戶抽象類 AccountAbstractFactory.WeiboUser = function() { this.type = "weibo"; } AccountAbstractFactory.WeiboUser.prototype = { getName: function() { return new Error("抽象方法不能調用"); } }
AccountAbstractFactory 就是一個抽象工廠方法,該方法在參數中傳遞子類和父類,在方法體內部實現了子類對父類的繼承。對抽象工廠方法添加抽象類的方法我們是通過點語法進行添加的。
下面我們來定義普通用戶的子類:
//普通微信用戶子類 function UserOfWechat(name) { this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } //抽象工廠實現WechatUser類的繼承 AccountAbstractFactory(UserOfWechat, "WechatUser"); //子類中重寫抽象方法 UserOfWechat.prototype.getName = function() { return this.name; } //普通qq用戶子類 function UserOfQq(name) { this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } //抽象工廠實現QqUser類的繼承 AccountAbstractFactory(UserOfQq, "QqUser"); //子類中重寫抽象方法 UserOfQq.prototype.getName = function() { return this.name; } //普通微博用戶子類 function UserOfWeibo(name) { this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } //抽象工廠實現WeiboUser類的繼承 AccountAbstractFactory(UserOfWeibo, "WeiboUser"); //子類中重寫抽象方法 UserOfWeibo.prototype.getName = function() { return this.name; }
上述代碼我們分別定義了UserOfWechat,UserOfQq,UserOfWeibo三種類。這三個類作為子類通過抽象工廠方法實現繼承。特別需要注意的是,調用抽象工廠方法后不要忘記重寫抽象方法,否則在子類的實例中調用抽象方法會報錯。
我們來分別對這三種類進行實例化,檢測抽象工廠方法是實現了類簇的管理。
//實例化微信用戶 let wechatUserA = new UserOfWechat("微信小李"); console.log(wechatUserA.getName(), wechatUserA.type); //微信小李 wechat let wechatUserB = new UserOfWechat("微信小王"); console.log(wechatUserB.getName(), wechatUserB.type); //微信小王 wechat //實例化qq用戶 let qqUserA = new UserOfQq("QQ小李"); console.log(qqUserA.getName(), qqUserA.type); //QQ小李 qq let qqUserB = new UserOfQq("QQ小王"); console.log(qqUserB.getName(), qqUserB.type); //QQ小王 qq //實例化微博用戶 let weiboUserA =new UserOfWeibo("微博小李"); console.log(weiboUserA.getName(), weiboUserA.type); //微博小李 weibo let weiboUserB =new UserOfWeibo("微博小王"); console.log(weiboUserB.getName(), weiboUserB.type); //微博小王 weibo
從打印結果上看,AccountAbstractFactory這個抽象工廠很好的實現了它的作用,將不同用戶賬戶按照社交媒體這一個類簇進行了分類。這就是抽象工廠的作用,它不直接創建實例,而是通過類的繼承進行類簇的管理。抽象工廠模式一般用在多人協作的超大型項目中,并且嚴格的要求項目以面向對象的思想進行完成。
2 ES6中的工廠模式ES6中給我們提供了class新語法,雖然class本質上是一顆語法糖,并也沒有改變JavaScript是使用原型繼承的語言,但是確實讓對象的創建和繼承的過程變得更加的清晰和易讀。下面我們使用ES6的新語法來重寫上面的例子。
2.1 ES6重寫簡單工廠模式使用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");2.2 ES6重寫工廠方法模式
在上文中我們提到,工廠方法模式的本意是將實際創建對象的工作推遲到子類中,這樣核心類就變成了抽象類。但是JavaScript的abstract是一個保留字,并沒有提供抽象類,所以之前我們只是借鑒了工廠方法模式的核心思想。
雖然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");2.3 ES6重寫抽象工廠模式
抽象工廠模式并不直接生成實例, 而是用于對產品類簇的創建。我們同樣使用new.target語法來模擬抽象類,并通過繼承的方式創建出UserOfWechat, UserOfQq, UserOfWeibo這一系列子類類簇。使用getAbstractUserFactor來返回指定的類簇。
class User { constructor(type) { if (new.target === User) { throw new Error("抽象類不能實例化!") } this.type = type; } } class UserOfWechat extends User { constructor(name) { super("wechat"); this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } } class UserOfQq extends User { constructor(name) { super("qq"); this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } } class UserOfWeibo extends User { constructor(name) { super("weibo"); this.name = name; this.viewPage = ["首頁", "通訊錄", "發現頁"] } } 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("微博小李");3 工廠模式的項目實戰應用
在實際的前端業務中,最常用的簡單工廠模式。如果不是超大型的項目,是很難有機會使用到工廠方法模式和抽象工廠方法模式的。下面我介紹在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方法添加的路由刷新后不能保存,所以會導致路由無法訪問。通常的做法是本地加密保存用戶信息,在刷新后獲取本地權限并解密,根據權限重新添加路由。這里因為和工廠模式沒有太大的關系就不再贅述。
總結上面說到的三種工廠模式和上文的單例模式一樣,都是屬于創建型的設計模式。簡單工廠模式又叫靜態工廠方法,用來創建某一種產品對象的實例,用來創建單一對象;工廠方法模式是將創建實例推遲到子類中進行;抽象工廠模式是對類的工廠抽象用來創建產品類簇,不負責創建某一類產品的實例。在實際的業務中,需要根據實際的業務復雜度來選擇合適的模式。對于非大型的前端應用來說,靈活使用簡單工廠其實就能解決大部分問題。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93980.html
摘要:簡單工廠模式簡單工廠模式又叫靜態工廠模式,由一個工廠對象決定創建某一種產品對象類的實例。工廠方法模式工廠方法模式的本意是將實際創建對象的工作推遲到子類中,這樣核心類就變成了抽象類。抽象工廠模式一般用在 1 什么是工廠模式? 工廠模式是用來創建對象的一種最常用的設計模式。我們不暴露創建對象的具體邏輯,而是將將邏輯封裝在一個函數中,那么這個函數就可以被視為一個工廠。工廠模式根據抽象程度的不...
摘要:書籍建造者類調用建造者高效能人士的七個習慣史蒂芬柯維勵志上面的這個類和第一個例子的效果一樣,但是長度確減少不少,在有更多屬性的時候,減少的代碼量會更為明顯。參考內容設計模式張容銘 showImg(https://segmentfault.com/img/remote/1460000015147692); 1 什么是建造者模式? 建造者模式(Builder)是將一個復雜對象的構建層與其表...
摘要:書籍建造者類調用建造者高效能人士的七個習慣史蒂芬柯維勵志上面的這個類和第一個例子的效果一樣,但是長度確減少不少,在有更多屬性的時候,減少的代碼量會更為明顯。參考內容設計模式張容銘 showImg(https://segmentfault.com/img/remote/1460000015147692); 1 什么是建造者模式? 建造者模式(Builder)是將一個復雜對象的構建層與其表...
閱讀 1858·2021-11-22 15:25
閱讀 3958·2021-11-17 09:33
閱讀 2526·2021-10-12 10:12
閱讀 1813·2021-10-09 09:44
閱讀 3243·2021-10-08 10:04
閱讀 1326·2021-09-29 09:35
閱讀 1960·2019-08-30 12:57
閱讀 1313·2019-08-29 16:22