前言
博客园谈设计模式的文章很多,我也受益匪浅,包括TerryLee、吕震宇等等的.NET设计模式系列文章,强烈推荐。对于我,擅长于前台代码的开发,对于设计模式也有一定的了解,于是我想结合Javascript来设计前台方面的“设计模式”,以对后台“设计模式”做个补充。开始这个系列我也诚惶诚恐,怕自己写得不好,不过我也想做个尝试,一来希望能给一些人有些帮助吧,二来从写文章中锻炼下自己,三来通过写文章对自己增加自信;如果写得不好,欢迎拍砖,我会虚心向博客园高手牛人们学习请教;如果觉得写得还可以,谢谢大家的支持了:)
概述
抽象工厂模式广泛应用于因为需求变动导致一些相似对象的创建工作,拿我们做项目最熟悉的多版本数据库,根据数据库的不同,有可能有SQLServer,Access,Oracle,MySQL版本等等的数据库,这样我们就能运用工厂模式,把各个数据库中相似的功能操作封装到它们各自的对象模型中,通过工厂对象统一创建各个对象实例,是客户程序和这些具体对象实现了松耦合;而工厂方法模式主要是针对某个对象的需求变更,但是这个对象不会随着它的变动而导致它所在的业务流程中的变动,它具有高聚合的能力,因此它的外部接口是很稳定的;
定义
抽象工厂模式是每个抽象产品派生多个具体产品类,每个抽象工厂派生多个具体工厂类,每个具体工厂负责多个(一系列)具体产品的实例创建。
工厂方法模式是每个抽象产品派生多个具体产品类,每个抽象工厂类派生多个具体工厂类,每个具体工厂类负责一个具体产品的实例创建。
类图
抽象工厂模式类图
工厂方法模式类图
实例分析
在开始工厂模式之前,有必要先介绍下简单工厂的说法,为了避免在客户程序的对象出现"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/)
二、接下来开始本篇的重点,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的方法做为抽象方法的具体实现;
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
"
;
//
从用户类型得到值,这里是个假设,包含三个类型用户:guest,blogmember,superadmin
var
color
=
"
blue
"
;
//
这里是初始化颜色,包括四种颜色:blue,green,red,violet
var
blockfactory;
//
布局工厂类的全局变量声明
(你可以通过用户登录将登录信息存入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代码完整实现如下:
前台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");
});
});
至此,抽象工厂和工厂方法模式的一些思路已经应用在该博客系统中。
这里实现链接实例:
抽象工厂和工厂方法模式Demo
源代码就不提供下载了,无非就是html,js,css文件,从链接实例中可以查看源代码;
总结
该篇文章用Javascript设计抽象工厂和工厂方法模式的思路,实现一个博客系统的构想。
本篇到此为止,谢谢大家阅读!
参考文献:《Head First Design Pattern》
本系列文章转载时请注明出处,谢谢合作!
相关系列文章:
Javascript乱弹设计模式系列(6) - 单件模式(Singleton)
Javascript乱弹设计模式系列(5) - 命令模式(Command)
Javascript乱弹设计模式系列(4) - 组合模式(Composite)
Javascript乱弹设计模式系列(3) - 装饰者模式(Decorator)
Javascript乱弹设计模式系列(2) - 抽象工厂以及工厂方法模式(Factory)
Javascript乱弹设计模式系列(1) - 观察者模式(Observer)
Javascript乱弹设计模式系列(0) - 面向对象基础以及接口和继承类的实现