组合模式
- 将对象组合成树形结构,以表示‘部分-整体’的层次结构;遍历树形结构的操作只需要一次操作;
- 利用对象多态性的统一对待组合对象和单个对象,不用关心他们的不同;
- 像命令模式中宏命令就是一种组合模式;
请求在树中传递的过程;
- 如果子节点是叶节点,叶对象自身会处理这个请求;如果还是组合对象则继续往下传递;
更强大的宏命令
- 由于宏命令和一般对象的执行都是
excute
;所有宏命令还可以组合
var macroCommand1 = MacroCommand();
macroCommand1.add(openCommand);
var macroCommand2 = MacroCommand();
macroCommand2.add(closeCommand);
var macroCommand = MacroCommand();
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
透明性带来的安全性问题
- 组合对象和叶对象还是有区别的:叶对象不能增加子节点
var openCommand = {
execute: function() {
console.log('open');
},
add: function() {
throw new Error('can not add')
}
}
组合模式例子-扫描文件夹
var Folder = function(name) {
this.name = name;
this.parent = null;
this.files = [];
};
Folder.prototype.add = function(file) {
file.parent = this;
this.files.push(file);
return this;
};
Folder.prototype.scan = function() {
console.log('开始扫描文件夹:' + this.name);
var i = 0, file, files = this.files;
for(; file = files[i++];)
file.scan();
};
Folder.prototype.remove = function() {
var parent = this.parent;
if(!parent)
return;
var files = parent.files, file;
for(var len = files.length - 1; len >= 0; len--) {
file = files[len];
if(file === this)
files.splice(len, 1);
}
};
var File = function(name) {
this.name = name;
this.parent = null;
};
File.prototype.add = function() {
throw new Error('文件下不能再添加文件');
};
File.prototype.scan = function() {
console.log('开始扫描文件:' + this.name);
};
File.prototype.remove = function() {
var parent = this.parent;
if(!parent)
return;
var files = parent.files, file;
for(var len = files.length - 1; len >= 0; len--) {
file = files[len];
if(file === this)
files.splice(len, 1);
}
};
var folder = new Folder( '学习资料' );
var folder1 = new Folder( 'JavaScript' );
folder1.add(new File( 'JavaScript 设计模式与开发实践' ));
folder.add( folder1 ).add(new File( '重构与模式' ));
folder1.remove();
folder.scan();
新闻模块
//虚拟父类
var News = function () {
this.children = [];
this.element = null;
}
News.prototype = {
init: function () {
throw new Error('请重写你的方法!');
},
add: function () {
throw new Error('请重写你的方法!');
},
getElement: function () {
console.log(this);
throw new Error('请重写你的方法!');
}
}
//容器类构造函数
var Container = function (id, parent) {
News.call(this);
this.id = id;
this.parent = parent;
this.init();
}
inheritPro(Container, News);
Container.prototype.init = function () {
this.element = document.createElement('ul');
this.element.id = this.id;
this.element.className = 'new-container';
}
Container.prototype.add = function (child) {
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
}
Container.prototype.getElement = function () {
return this.element;
}
Container.prototype.show = function () {
this.parent.appendChild(this.element);
}
//成员集合类
var Item = function (classname) {
News.call(this);
this.classname = classname || '';
this.init();
}
inheritPro(Item, News);
Item.prototype.init = function () {
this.element = document.createElement('li');
this.element.className = this.classname;
}
Item.prototype.add = function (child) {...}
Item.prototype.getElement = function () {...}
//其它基类
NewsGroup..
ImageNews..
IconNews..
EasyNews..
TypeNews..
//构建
var news1 = new Container('news', document.body);
news1.add(
new Item('normal').add(
new IconNews('...', '#', 'video')
)
).add(
new Item('normal').add(
new NewsGroup('has-img', '#', 'small').add(
new ImageNews('img/1.jpg', '#', 'small')
).add(
new EasyNews('...', '#')
).add(
new EasyNews('...', '#')
)
)
).add(
new Item('normal').add(
new TypeNews('...', '#', '..', 'left')
)
).show();
值得注意的地方
- 组合模式是一种聚合关系不是父子关系;
- 要求组合对象和叶对象拥有相同的接口;同时一组叶对象的操作也必须一致;
- 在一些复合情况下必须给父节点和子节点建立双向映射关系;最简单的方法就是互相保存对方的引用,可用引入中介者来管理这些对象;
- 在一些结构复杂的树中,应该避免便利整棵树,可用借助职责链模式;
组合模式的使用情况
- 表示对象的部分-整体层次结构,特别是不确定有多少层次的时候;
- 希望统一对象树中的所有对象的时候;
模版方法模式
- 是一种典型得通过封装变化提高系统扩展性的设计模式;
- 模版方法模式由两部分组成:
- 模版方式方法是一种严重依赖抽象类的设计模式;JS并没有从语法层面提供对抽象类的支持;JS中不能保证子类是否重写父类方法
- 简单的解决方法:给子类要重写的父类方法设置抛出异常
Father.prototype.method = function() {
throw new Error('子类必须重写这个方法');
}
使用场景
- web开发中,比如构建一系列UI组件的一般过程:
- 1.初始化一个
div
容器
- 2.通过
ajax
请求相应内容
- 3.把数据渲染到容器中
- 4.通知用户组建渲染完毕
- 一般第2步请求地址不同,第3步渲染方式不同;所以可以把这4步抽象到父类的模版中,父类还可以提供1,4步的具体实现;当子类基础这个父类后再重写模版方法中的2,3步;
钩子方法
- 一般模版方法里的步骤是确定好的,具体情况下可以加入钩子;通过其返回的结果来调整顺序或跳过步骤;
好莱坞原则
- 允许底层组件将自己挂钩到高层组建中,高层组件会决定什么时候以何种方式去使用底层组建;
- 用模版方式编写时,意味着子类放弃了对自己的控制权,其只负责提供一些设计上的细节;
- 这个原则也应用于观察者模式和回调函数;
去处继承
- 模版方法模式是为数不多基于基础的设计模式,但JS语言实际并没有提供真正的类式继承;
var Father = function(param){
var first = param.first || function() {
console.log('first');
};
var second = param.second || function() {
throw new Error('必须传递second方法');
};
var F = function() {};
F.prototype.init = function(){
first();
second();
}
return F;
};
var Child = Father({
second: function() {
console.log('second');
}
});
var child = new Child();
child.init();
弹出框例子
//模版
var Alert = function (data) {
if(!data) return;
this.content = data.content;
this.panel = document.createElement('div');
this.panel.className = 'alert';
this.contentNode = document.createElement('p');
this.contentNode.innerHTML = this.content;
this.confirmBtn = document.createElement('span');
this.confirmBtn.className = 'a-confirm';
this.confirmBtn.innerHTML = data.confirm || '确认';
this.closeBtn = document.createElement('b');
this.closeBtn.className = 'a-close';
this.closeBtn.innerHTML = data.close || 'x';
this.success = data.success || function() {};
this.fail = data.fail || function () {};
this.init();
}
Alert.prototype = {
init: function () {
this.panel.appendChild(this.closeBtn);
this.panel.appendChild(this.contentNode);
this.panel.appendChild(this.confirmBtn);
document.body.appendChild(this.panel);
this.bindEvent();
this.show();
},
bindEvent: function () {
var self = this;
this.closeBtn.onclick = function () {
self.fail();
self.hide();
}
this.confirmBtn.onclick = function () {
self.success();
self.hide();
}
},
hide: function () {
this.panel.style.display = 'none';
},
show: function () {
this.panel.style.display = 'block';
}
}
//根据模版创建类
var RightAlert = function (data) {
Alert.call(this, data);
this.confirmBtn.className = this.confirmBtn.className + ' right';
}
RightAlert.prototype = new Alert();
导航栏例子
//一个格式化方法
function formateString (str, data) {
return str.replace(/{#(w+)#}/g, function (match, key) {
return typeof data[key] === undefined ? '' : data[key];
})
}
//基础导航
var Nav = function (data) {
this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';
this.html = '';
for(var i = 0, l = data.length; i < l; i++) {
this.html += formateString(this.item, data[i]);
}
return this.html;
}
//带有消息提醒的导航
var NumNav = function (data) {
var tpl = '<b>{#num#}</b>';
for(var i = data.length - 1; i >= 0; i--) {
data[i].name += formateString(tpl, data[i]);
}
return Nav.call(this, data);
}
var nav = document.getElementById('container');
nav.innerHTML = NumNav([
{
href: 'http://www.baidu.com',
title: 'baidu',
name: '百度',
num: '10'
},
{
href: 'http://www.taobao.com',
title: 'taobao',
name: '淘宝',
num: '2'
}
])
- 其实在JS中不一定非要用模版方法,可能选择高阶函数更合适;