前言
博客园谈设计模式的文章很多,我也受益匪浅,包括TerryLee、吕震宇等等的.NET设计模式系列文章,强烈推荐。对于我,擅长于前台代码的开发,对于设计模式也有一定的了解,于是我想结合Javascript来设计前台方面的“设计模式”,以对后台“设计模式”做个补充。开始这个系列我也诚惶诚恐,怕自己写得不好,不过我也想做个尝试,一来希望能给一些人有些帮助吧,二来从写文章中锻炼下自己,三来通过写文章对自己增加自信;如果写得不好,欢迎拍砖,我会虚心向博客园高手牛人们学习请教;如果觉得写得还可以,谢谢大家的支持了:)
组合模式比较简单,那么开始今天的文章。
概述
它又是部分-整体的模式,元素有两种形式,一种是简单元素,一种是复杂元素,其中复杂元素是简单元素的组成,所以客户程序要操作复杂元素时,有时候它并不想了解内部包括哪些简单元素,但是又想能够保持简单元素的简易操作性,于是,就有了组合模式,它可以抽象成一个树状结构,其中简单元素为叶子结点,而复杂元素为非叶子结点。
定义
组合模式允许你将对象组合成树形结构以表示“整体/部分”的层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
类图
实例分析
这里我给出一个利用组合模式设计的菜单导航:
先看下效果图:
现在开始分析通过组合模式如何实现它:
1. 引用InterfaceAndClass.js文件,作为接口的构造和类的继承,这个不多说了,详细请看前面的文章;
2. 添加MenuComponent.js文件,作为Component接口或者抽象类,这里我用了接口的形式:
var
MenuComponent
=
new
Interface(
"
MenuComponent
"
, [[
"
getValue
"
]]);
3. 添加MenuItem.js文件,作为Leaf简单元素,这里指菜单的叶子结点:
function
MenuItem(text, title, href) {
this
.text
=
text;
this
.title
=
title;
this
.href
=
href;
Interface.registerImplements(
this
, MenuComponent);
}
MenuItem.prototype
=
{
getValue :
function
() {
//
}
}
其中text为菜单上显示的文本,title为提示文本符,href为链接导向;并且让它继承MenuComponent接口;
4. 添加Menu.js文件,作为Composite复合元素,它是由一系列的Leaf简单元素组成的:
function
Menu(text, title, href) {
this
.menuComponents
=
new
Array();
this
.text
=
text;
this
.title
=
title;
this
.href
=
href;
Interface.registerImplements(
this
, MenuComponent);
}
Menu.prototype
=
{
getValue :
function
() {
//
},
add :
function
(component) {
this
.menuComponents.push(component);
},
remove :
function
(component) {
for
(
var
i
=
0
, len
=
this
.menuComponents.length; i
<
len; i
++
)
{
if
(
this
.menuComponents[i]
==
component)
{
this
.menuComponents.splice(i,
1
);
break
;
}
}
},
removeAt :
function
(index) {
if
(
this
.menuComponents.length
<=
index)
{
this
.menuComponents.splice(index,
1
);
}
else
{
throw
new
Error(
"
索引操作数组超过上限
"
);
}
}
}
它继承MenuAComponent接口,因此它和MenuItem使用同一个接口,这样可以统一简单元素和复杂元素的方法调用,add方法用来添加component类,这里的component可以是Menu,也可以是MenuItem;remove方法用来删除指定的component类;removeAt方法用过索引删除相应的component类;getValue方法取得菜单数据;
让我们先从实例上得到一个树状图:
从图上可以看出,实际上菜单1,菜单2-1,菜单2-2-1,菜单2-2-2,菜单2-3,菜单3-1,菜单4都是属于Leaf,即MenuItem类;
菜单2,菜单2-2,菜单3都属于Composite,即Menu类;
这样Menu类的GetValue方法可以通过遍历它的子结点数组,得到所有子结点菜单数据;
for
(
var
i
=
0
, len
=
this
.menuComponents.length; i
<
len; i
++
)
{
str
+=
this
.menuComponents[i].getValue();
}
其中this.menuComponents[i].getValue()可以是Menu类的getValue方法,也可以是MenuItem类的getValue方法;
这样就可以完成了所有结点的遍历。
然后给出MenuItem.js和Menu.js的完整代码如下:
MenuItem.js
function MenuItem(text, title, href) {
this.text = text;
this.title = title;
this.href = href;
Interface.registerImplements(this, MenuComponent);
}
MenuItem.prototype = {
getValue : function() {
var str = "<li class=\"Menu-Leaf\" title=\"" + this.title + "\"><a href=\"" + this.href + "\">" + this.text + "</a></li>";
return str;
}
}
Menu.js
function Menu(text, title, href) {
this.menuComponents = new Array();
this.text = text;
this.title = title;
this.href = href;
Interface.registerImplements(this, MenuComponent);
}
Menu.prototype = {
getValue : function() {
if(this.menuComponents.length == 0)
{
throw new Error(this.text + "菜单下没有子菜单");
}
var str = "<li class=\"Menu-WithChildren\" title=\"" + this.title + "\"><a class=\"Menu-Link\" href=\"" + this.href + "\">" + this.text + "</a>";
str += "<ul>";
for(var i = 0, len = this.menuComponents.length; i < len; i++)
{
str += this.menuComponents[i].getValue();
}
str += "</ul>";
return str;
},
add : function(component) {
this.menuComponents.push(component);
},
remove : function(component) {
for(var i = 0, len = this.menuComponents.length; i < len; i++)
{
if(this.menuComponents[i] == component)
{
this.menuComponents.splice(i,1);
break;
}
}
},
removeAt : function(index) {
if(this.menuComponents.length <= index)
{
this.menuComponents.splice(index, 1);
}
else
{
throw new Error("索引操作数组超过上限");
}
}
}
5. 接着添加一个Menu操作类MenuOpr:
var
MenuOpr
=
{
list :
new
Array(),
add :
function
(component) {
this
.list.push(component);
},
print :
function
(container) {
var
str
=
"
<ul class=\
"
Menu\
"
>
"
;
for
(
var
i
=
0
, len
=
this
.list.length; i
<
len; i
++
) {
str
+=
this
.list[i].getValue();
}
document.getElementById(container).innerHTML
=
str
+
"
</ul>
"
;
}
}
6. 最后利用组合模式编写调用代码:
var
menu1
=
new
MenuItem(
"
菜单1
"
,
"
菜单1
"
,
"
#
"
);
var
menu2
=
new
Menu(
"
菜单2
"
,
"
菜单2
"
,
"
#
"
);
var
menu2_1
=
new
MenuItem(
"
菜单2-1
"
,
"
菜单2-1
"
,
"
#
"
);
var
menu2_2
=
new
Menu(
"
菜单2-2
"
,
"
菜单2-2
"
,
"
#
"
);
var
menu2_2_1
=
new
MenuItem(
"
菜单2-2-1
"
,
"
菜单2-2-1
"
,
"
#
"
);
var
menu2_2_2
=
new
MenuItem(
"
菜单2-2-2
"
,
"
菜单2-2-2
"
,
"
#
"
);
var
menu2_3
=
new
MenuItem(
"
菜单2-3
"
,
"
菜单2-3
"
,
"
#
"
);
menu2.add(menu2_1);
menu2.add(menu2_2);
menu2_2.add(menu2_2_1);
menu2_2.add(menu2_2_2);
menu2.add(menu2_3);
var
menu3
=
new
Menu(
"
菜单3
"
,
"
菜单3
"
,
"
#
"
);
var
menu3_1
=
new
MenuItem(
"
菜单3-1
"
,
"
菜单3-1
"
,
"
#
"
);
menu3.add(menu3_1);
var
menu4
=
new
MenuItem(
"
菜单4
"
,
"
菜单4
"
,
"
#
"
);
MenuOpr.add(menu1);
MenuOpr.add(menu2);
MenuOpr.add(menu3);
MenuOpr.add(menu4);
MenuOpr.print(
"
main_container
"
);
main_container为一个div层的id,这里代码就不多说了,一看就看得懂的。
当然实现这个菜单还是有不少方法的,而我这肯定不是最优的,这里我只是提供一个利用组合模式的思路而已;
如果按照常规,你可以把MenuItem都整合到Menu上,即MenuItem都改为Menu,这样的代码,你是不是更喜欢点呢?
这个就留给大家自己研究吧!
最后,说下由于文中实例的菜单样式采用li:hover的方式来弹出下级菜单,由于这个方式对于IE7以下版本是无效的,所以这里引用了cssfriendly开源项目中的两个JS文件,MenuAdapter.js和AdapterUtils.js,所以把它们放进项目中引用进来,这样该菜单就兼容了IE7以下版本的浏览器。
附:源代码下载
总结
该篇文章用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) - 面向对象基础以及接口和继承类的实现