JavaScript設計模式之抽象工廠及工廠方法模式
概述
抽象工廠模式廣泛應用于因為需求變動導致一些相似對象的創建工作,拿我們做項目最熟悉的多版本數據庫,根據數據庫的不同,有可能有SQL Server,Access,Oracle,My SQL版本等等的數據庫,這樣我們就能運用工廠模式,把各個數據庫中相似的功能操作封裝到它們各自的對象模型中,通過工廠對象統一創建各個對象實例,是客戶程序和這些具體對象實現了松耦合;而工廠方法模式主要是針對某個對象的需求變更,但是這個對象不會隨著它的變動而導致它所在的業務流程中的變動,它具有高聚合的能力,因此它的外部接口是很穩定的;
定義
抽象工廠模式是每個抽象產品派生多個具體產品類,每個抽象工廠派生多個具體工廠類,每個具體工廠負責多個(一系列)具體產品的實例創建。
工廠方法模式是每個抽象產品派生多個具體產品類,每個抽象工廠類派生多個具體工廠類,每個具體工廠類負責一個具體產品的實例創建。
類圖
抽象工廠模式類圖

工廠方法模式類圖

#p#
實例分析
在開始工廠模式之前,有必要先介紹下簡單工廠的說法,為了避免在客戶程序的對象出現"if...else..."代碼難以擴展以及維護,這里創建一個工廠類來封裝這些對象,那這個就應用了簡單工廠的方式。
這個場景是這樣,有個博客網站,在網站中用戶分為幾種類型,我這里暫且暫且分為游客,博客會員,超級管理員三種用戶權限:
◆游客只能進行簡單的查看博客的文章,個人資料等等;
◆博客會員還可以對自己的博客進行管理;
◆超級管理員還可以對于博客系統后臺進行管理。
在開始介紹之前,先貼出前兩篇介紹的一些接口和類繼承的JS文件InterfaceAndClass.js,這里主要要說的是類繼承的寫法:
function inheritClass(subClass, superClass)
{
var Func = function() {};
for(p in superClass.prototype)
{
Func.prototype[p] = superClass.prototype[p];
}
subClass.prototype = new Func();
subClass.prototype.constructor = subClass;
}
|
這里將父類superClass原型的所有方法賦值給新創建的函數類,之后把函數類的原型賦值于子類subClass原型中。
一、現在先對JavaScript簡單工廠進行介紹:
1. 添加用戶類型接口IMember.js:
var IMember = new Interface("IMember", [["getMemberInfo"]]);
|
getMemberInfo做為IMember接口的接口方法。
2. 添加三個具體用戶類型的類Guest.js,BlogMember.js,SuperAdmin.js,繼承IMember接口:
//游客類
function Guest() {
Interface.registerImplements(this, IMember); //繼承IMember接口
}
Guest.prototype = {
getMemberInfo : function() {
return "游客";
},
getData : function() {
return "";
}
}
//博客會員類
function BlogMember() {
Interface.registerImplements(this, IMember);
}
BlogMember.prototype = {
getMemberInfo : function() {
return "博客會員";
},
getData : function() {
return "";
}
}
//超級管理員類
function SuperAdmin() {
Interface.registerImplements(this, IMember);
}
SuperAdmin.prototype = {
getMemberInfo : function() {
return "超級管理員";
},
getData : function() {
return "";
}
}
3. 創建一個用戶類型工廠類來封裝這些用戶類型的操作,添加MemberFactory.js:
var MemberFactory = {
createMemberType : function(memberType) {
var _memberType;
switch(memberType)
{
case "guest": _memberType = new Guest(); break;
case "blogmember": _memberType = new BlogMember(); break;
case "superadmin": _memberType = new SuperAdmin(); break;
default: _memberType = new BlogMember(); break;
}
return _memberType;
}
}
|
通過memberType的“枚舉”,創建相應的用戶類型類的對象;
4. 至此,通過MemberFactory.createMemberType的“靜態方法”返回一個用戶類型對象;
var member = MemberFactory.createMemberType("guest"); //guest, blogmember, superadmin
$("#result").html("您當前為:" + member.getMemberInfo());
|
(這里$(“…”)寫法是jquery類庫中的語法,詳細請看官方文檔http://jquery.com/)
#p#
二、接下來開始本篇的重點,JavaScript工廠模式的網站應用
1. 延續上面的博客網站的場景:
游客只能進行簡單的查看博客的文章,個人資料等等;
博客會員還可以對自己的博客進行管理;
超級管理員還可以對于博客系統后臺進行管理;
這里有這些標簽Tab:首頁,文章,電影,音樂,相冊,關于我,文章管理,個人資料管理,系統后臺管理,其中 游客只能訪問“首頁,文章,電影,音樂,相冊,關于我”,博客會員(登錄后)增加訪問“文章管理,個人資料管理”,超級管理員增加訪問“系統后臺管理”;另外博客會員和超級管理員擁有修改皮膚顏色和版塊類型的功能;
最終頁面顯示如下所示:

從圖上可以看出,博客會員以上的用戶類型可以顯示“文章管理,個人資料管理”標簽,超級管理員可以顯示“系統后臺管理”;而布局選擇包括“左,中,右結構”,“左,右上,右下結構”,“左上,左下,右結構”,顏色包括“藍”,“紅”,“綠”,“紫”,這里只能博客會員和超級管理員才可以顯示;
(這里我使用了網上提供的jquery.tab.js插件對標簽進行實現,詳細請看http://stilbuero.de/jquery/tabs_3/)
好了,現在開始介紹這個實例通過JavaScript工廠模式是如何實現的。
2. IMember.js不變,從簡單工廠中直接復制。
3. IMember的具體實現類Guest,BlogMember,SuperAdmin從簡單工廠中復制,這里分別添加個原型方法isSetColorBlock,判斷該用戶類型是否可設置顏色和布局,如下所示:
Guest.prototype = {
//…
isSetColorBlock : function() {
return false;
}
}
BlogMember.prototype = {
//…
isSetColorBlock : function() {
return true;
}
}
SuperAdmin.prototype = {
//…
isSetColorBlock : function() {
return true;
}
}
|
可以看到游客不能進行設置,而博客會員和超級管理能進行設置;
4. MemberFactory.js不變,從簡單工廠直接復制。
5. 添加IBlock.js,創建布局接口:
var IBlock = new Interface("IBlock", [["getData"]]);
|
6. 實現它的具體類,這里添加LMRBlock.js(左 中 右 布局),LRMBlock.js(左上 左下 右 布局),MLRBlock.js(左 右上 右下 布局),這里以LMRBlock.js為例:
// 左、中、右結構樣式版塊
function LMRBlock()
{
this.color = "blue";
Interface.registerImplements(this, IBlock); //繼承布局IBlock接口
}
LMRBlock.prototype = {
displayBlock : function() {
this.getData();
// 具體布局實現
},
getData : function() {
return new Error("抽象方法,不能調用");
}
}
|
這里首先創建的是類似于一個抽象類,該類首先繼承于布局接口,從代碼中可以看出getData方法的實現返回錯誤異常,實際上它作為一個抽象方法,不需要實現任何東西;這里displayBlock方法中調用它的抽象方法,這里就是典型的抽象方法模式,以備于它的子類繼承實現它的抽象方法;
現在看看它的子類有哪些:
function BlueLMRBlock(){
}
inheritClass(BlueLMRBlock, LMRBlock); //繼承LMRBlock抽象類
BlueLMRBlock.prototype.getData = function() { //父類抽象方法的具體實現
$(".tabs-nav a, .tabs-nav a span").css({"background-image":"url(script/tab/tab_blue.png)"});
this.color = "blue";
}
function GreenLMRBlock(){
}
inheritClass(GreenLMRBlock, LMRBlock);
GreenLMRBlock.prototype.getData = function() {
$(".tabs-nav a, .tabs-nav a span").css({"background-image":"url(script/tab/tab_green.png)"});
this.color = "green";
}
function RedLMRBlock(){
}
inheritClass(RedLMRBlock, LMRBlock);
RedLMRBlock.prototype.getData = function() {
$(".tabs-nav a, .tabs-nav a span").css({"background-image":"url(script/tab/tab_red.png)"});
this.color = "red";
}
function VioletLMRBlock(){
}
inheritClass(VioletLMRBlock, LMRBlock);
VioletLMRBlock.prototype.getData = function() {
$(".tabs-nav a, .tabs-nav a span").css({"background-image":"url(script/tab/tab_violet.png)"});
this.color = "violet";
}
這里包括4種顏色的子類,全部都繼承于抽象類LMRBlock,子類中getData的方法做為抽象方法的具體實現;
#p#
7. 現在該創建個工廠來實現它們了,添加BlockFactory.js文件,首先創建布局抽象工廠類:
function BlockFactory(){
}
BlockFactory.prototype = {
getBlock : function(block) {
var _block;
_block = this.createBlock(block);
// 添加其他邏輯
//
return _block;
},
createBlock : function(block) {
return new Error("抽象方法,不能調用");
},
getBlockText : function() {
return new Error("抽象方法,不能調用");
}
};
|
這里createBlock和getBlockText同樣是做為抽象方法;
現在要創建三個繼承于這個布局抽象工廠類,LMRBlockFactory,LRMBlockFactory,MLRBlockFactory,這里同樣以LMRBlockFactory為例:
function LMRBlockFactory(){
}
inheritClass(LMRBlockFactory,BlockFactory);
LMRBlockFactory.prototype.createBlock = function(block) {
var _block;
switch(block)
{
case "blue": _block = new BlueLMRBlock(); break;
case "red": _block = new RedLMRBlock(); break;
case "green": _block = new GreenLMRBlock(); break;
case "violet": _block = new VioletLMRBlock(); break;
default: _block = new BlueLMRBlock(); break;
}
return _block;
};
LMRBlockFactory.prototype.getBlockText = function() {
return "LMR";
};
|
LMRBlockFactory繼承于布局抽象工廠類,實現它的抽象方法createBlock和getBlockText,其中creatBlock通過參數值,創建對應的布局實現類,這里用到了典型的抽象工廠模式;
8. 好了,一切都具備好了,現在開始討論我們的前臺使用了,添加factory.html,引用該引用的JS文件,這里列出一些核心代碼:
1) 添加初始化數據
var membertype = "superadmin"; //從用戶類型得到值,這里是個假設,包含三個類型用戶: |
(你可以通過用戶登錄將登錄信息存入cookies中,從cookies獲取用戶類型和用戶選擇色調)
2) 初始化用戶類型
// 初始化用戶類型
var member = MemberFactory.createMemberType(membertype);
$("#spanMemberType").html(member.getMemberInfo());
$("#container-1 li[id^='li']").css("display","block");
$("#container-1 li[id^='li']").each(function(index){
var arr = $(this).attr("power").split('|'); //取得對應標簽的權限數組
if(arr.indexOf(membertype) == -1) //權限比較
{
$(this).css("display","none");
$("#fragment-" + (index+1)).css("display","none");
}
});
if(member.isSetColorBlock()) //是否可設置布局和顏色
$("#Set").css("display","block");
else
$("#Set").css("display","none");
$("#selMemberType").val(membertype);
|
通過var member = MemberFactory.createMemberType(membertype);獲取用戶對象,通過用戶對象判斷是否可設置布局和顏色;
3) 初始化版塊類型和顏色類型
// 初始化版塊類型和顏色類型
blockfactory = new LMRBlockFactory();
var block = blockfactory.getBlock(color);
block.displayBlock();
$("img[id^='imgcolor_']").removeClass().addClass("color-unselected");
$("#imgcolor_" + color).removeClass().addClass("color-selected");
|
通過創建工廠對象,從顏色中獲取布局實現類的對象,然后通過調用displayBlock方法的實現初始化界面的布局;
前臺JS代碼完整實現如下:
var membertype = "superadmin"; //從用戶類型得到值,這里是個假設,包含三個類型用戶:guest,blogmember,superadmin
var color = "blue"; //這里是初始化顏色,包括四種顏色:blue,green,red,violet
var blockfactory; //布局工廠類的全局變量聲明
$(function() {
$("#container-1").tabs();
// 初始化用戶類型
var member = MemberFactory.createMemberType(membertype);
$("#spanMemberType").html(member.getMemberInfo());
$("#container-1 li[id^='li']").css("display","block");
$("#container-1 li[id^='li']").each(function(index){
var arr = $(this).attr("power").split('|'); //取得對應標簽的權限數組
if(arr.indexOf(membertype) == -1) //權限比較
{
$(this).css("display","none");
$("#fragment-" + (index+1)).css("display","none");
}
});
if(member.isSetColorBlock()) //是否可設置布局和顏色
$("#Set").css("display","block");
else
$("#Set").css("display","none");
$("#selMemberType").val(membertype);
// 初始化版塊類型和顏色類型
blockfactory = new LMRBlockFactory();
var block = blockfactory.getBlock(color);
block.displayBlock();
$("img[id^='imgcolor_']").removeClass().addClass("color-unselected");
$("#imgcolor_" + color).removeClass().addClass("color-selected");
// 用戶類型下拉框綁定
$("#selMemberType").bind("change", function() {
$('#container-1').triggerTab(1);
var mt = $(this).val();
membertype = MemberFactory.createMemberType(mt);
$("#spanMemberType").html(membertype.getMemberInfo());
$("#container-1 li[id^='li']").css("display","block");
$("#container-1 li[id^='li']").each(function(index){
var arr = $(this).attr("power").split('|');
if(arr.indexOf(mt) == -1)
{
$(this).css("display","none");
$("#fragment-" + (index+1)).css("display","none");
}
});
if(membertype.isSetColorBlock())
$("#Set").css("display","block");
else
$("#Set").css("display","none");
});
// 版塊類型選擇
$("img[id^='imgblock_']").bind("click", function() {
if($(this).className != "block-selected")
{
$("img[id^='imgblock_']").removeClass().addClass("block-unselected");
$(this).removeClass().addClass("block-selected");
}
var blocktext = $(this).attr("id").substring(9);
switch(blocktext)
{
case "LMR": blockfactory = new LMRBlockFactory(); break;
case "LRM": blockfactory = new LRMBlockFactory(); break;
case "MLR": blockfactory = new MLRBlockFactory(); break;
default: blockfactory = new LMRBlockFactory(); break;
}
var block = blockfactory.getBlock(color);
block.displayBlock();
});
// 顏色選擇
$("img[id^='imgcolor_']").bind("click", function() {
color = $(this).attr("id").substring(9);
var block = blockfactory.getBlock(color);
block.displayBlock();
$("img[id^='imgcolor_']").removeClass().addClass("color-unselected");
$("#imgcolor_" + color).removeClass().addClass("color-selected");
});
});
至此,抽象工廠和工廠方法模式的一些思路已經應用在該博客系統中。
【編輯推薦】

























