结构型设计模式关注于如何将类或对象组合成更大更复杂的结构,以简化设计。
1,外观模式
外观模式为一组复杂的子系统接口提供一个更高级的统一接口,通过这个借口来对子系统接口的访问更容易。在JavaScript中有时也会用于对底层结构兼容性做统一封装来简化用户使用。
1.1 添加一个点击事件
使用如下的代码添加一个点击事件时造成的问题.
document.onclick = function(e){
e.preventDefault();
if(e.target !== document.getElementById('myinput')){
hidePageAlert();
}
}
function hidePageAlert(){
//隐藏提示框
}
onclick事件是DOM0级事件,这种方式绑定的事件相当于为元素绑定了一个事件方法。可以使用DOM2级的addEventListener来实现,甚至在某些老版本IE中还需要用attachEvent来实现。
1.2 兼容方式
//外观模式实现
funciton addEvent(dom,type,fn){
//对于支持DOM2级事件处理程序addEventListener方法的浏览器
if(dom.addEventListener){
dom.addEventListener(type,fn,false);
//对于不支持addEventListener方法但支持attachEvent方法的浏览器
}else if(dom.attachEvent){
dom.attachEvent('on' + type,fn);
//对于不支持addEventListener方法也不支持attachEvent方法,但支持on+'事件名'的浏览器
}else{
dom['on'+type] = fn;
}
}
使用这种方法就可以放心的在开发中为元素绑定事件了.如下:
var myInput = document.getElementById('myInput');
addEvent(myInput,'click',function(){
console.log('绑定第一个事件')
});
addEvent(myInput,'click',function(){
console.log('绑定第二个事件')
});
1.3 除此之外
外观模式可以简化底层接口复杂性,也可以解决浏览器兼容性问题。而在第一节中写的代码除了绑定事件问题外,另外两处问题是在其他IE低版本中不兼容e.preventDefaule()和e.target。 也可以通过外观模式解决。
//获取事件对象
var getEvent = function(event){
//标准浏览器返回event,IE下window.event
return event || window.event;
}
//获取元素
var getTarget = function(event){
var event = getEvent(event);
//标准浏览器下event.target,IE下event.xrcElement
return event.target || event.srcElement;
}
//阻止默认行为
var preventDefault = function(event){
var event = getEvent(event);
//标准浏览器下
if(event.preventDefault){
event.preventDefault();
//IE浏览器
}else{
event.returnValue = false;
}
}
有个上面的方法,可以使用兼容的简单方式来解决上面的问题。
document.onclick = function(e){
//阻止默认行为
preventDefault(e);
//获取事件源目标对象
if(getTarget(e) !== document.getElementById('myinput')){
hideInputSug();
}
}
1.4,小型代码库
外观模式可以将浏览器不兼容的方法变得简单而又兼容各个浏览器,然而这只是外观模式应用的一部分,很多代码库都是通过外观模式来封装多个功能,简化底层操作方法,比如实现获取元素的属性样式的简单方法库。
//简约版属性样式方法库
var A = {
//通过id获取元素
g:function(id){
return document.getElementById(id);
},
//设置元素CSS属性
CSS:function(id,key,value){
document.getElementById(id).style[key] = value;
},
//设置元素的属性
attr:function(id,key,value){
docuemnt.getElementById(id)[key] = value;
},
html:function(id,html){
document.getElementById(id).innerHTML = html;
},
//为元素绑定事件
on:function(id,type,fn){
document.getElementById(id)['on'+type] = fn;
}
};
"通过这个代码库,我们再操作元素的属性样式时变得更简单。"
A.css('box','background','red'); //设置CSS样式
A.attr('box','className','box'); //设置class
A.html('box','这是新添加的内容'); //设置内容
A.on('box','click',function(){ //绑定事件
A.css('box','width','500px');
});
2 适配器模式
适配器模式:将一个类(对象)的接口(方法或属性)转化成另外一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得到解决。
2.1 使用适配器适配jQuery框架
如下是自己框架的代码
//定义框架
var A = A||{};
//通过ID获取元素
A.g = function(id){
return document.getElementById(id)
}
//为元素绑定事件
A.on = function(id,type,fn){
//如果传递参赛是字符串则以id处理,否则以元素对象处理
var dom = typeof id === 'string'? this.g(id) : id;
//标准DOM2级添加事件方式
if(dom.addEventListener){
dom.addEventListener(type,fn,false);
//IE DOM2级添加事件方式
}else if(dom.attachEvent){
dom.attachEvent('on' + type,fn);
//简易添加事件方式
}else{
dom['on' + type] = fn;
}
}
要将该框架和jQuery框架进行适配,可以下一个如下适配器
A.g = function(id){
//通过jQuery获取jQuery对象,然后返回第一个成员
return $(id).get(0);
}
A.on = function(id,type,fn){
//如果传递参赛是字符串则以id处理,否则以元素对象处理
var dom = typeof id === 'string' ? $('#'+id):$(id);
dom.on(type,fn);
}
2.2,参数适配器
除此之外,适配器还有很多用途,比如使用适配器适配参数
var arr = ['vavascript','book','前端编程语言','8月8日'];
var obj = {
name:'',
type:'',
title:'',
time:''
}
function arrToObjAdapter(err){
return{
name:arr[0],
type:arr[1],
title:arr[2],
data:arr[3]
}
}
3 代理模式
代理模式指由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。
由于用户相册模块上传的照片量越来越大,导致服务器端需要将图片上传模块重新部署到另外一个域(可理解为另一台服务器)中,这样对于前端来说,用户上传图片的请求路径发生变化,指向其他服务器,这就导致跨域问题。
.... 先留着.... 这块以后写.
4,装饰器模式
装饰器模式在不改变原对象的基础上,通过对其进行保证拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。
下面是一个对输入框添加新需求时使用装饰器模式添加的例子
装饰器函数如下:
//装饰者
var decorator = function(input,fn){
//获取事件源
var input = document.getElementById(input);
//若事件源已经绑定对象
if(typeof input.onclick === 'function'){
//缓存事件源原有回调函数
var oldClickFn = input.onclick;
//为事件源定义新的事件
input.onclick = function(){
//事件源原有回调函数
oldClickFn();
//执行事件源新增回调函数
fn();
}
}else{
//事件源未绑定事件,直接为事件源添加新增回调函数
input.onclick = fn;
}
//做其他事情
}
下面是使用装饰者装饰的用例:
//电话输入框功能装饰
decorator('tel_input',function(){
document.getElementById('tel_demo_text').style.display = 'none';
});
//姓名输入框功能装饰
decorator('name_input',function(){
document.getElementById('name_demo_text').style.display = 'none';
});
5,桥接模式
桥接模式在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。
使用桥接模式解耦的一个例子:
未使用桥接前如下:
var spans = document.getElementsByTagName('span');
//为用户名绑定特效
spans[0].onmouseover = function(){
this.style.color = 'red';
this.style.background = '#ddd';
};
spans[0].onmouseout = function(){
this.style.color = '#333';
this.style.background = '#f5f5f5';
};
//为等级绑定特效
spans[1].onmouseover = function(){
this.getElementsBuTagName('strong')[0].style.color = 'red';
this.getElementsBuTagName('strong')[0].style.background = '#ddd';
};
spans[1].onmouseout = function(){
this.getElementsBuTagName('strong')[0].style.color = '#333';
this.getElementsByTagName('strong')[0].style.background = '#f5f5f5';
};
使用桥接:
//抽象
function changeColor(dom,color,bg){
//设置元素的字体颜色
dom.style.color = color;
//设置元素的背景颜色
dom.style.background = bg;
}
var spans = document.getElementsByTagName('span');
spans[0].onmouseover = function(){
changeColor(this,'red','#ddd');
}
spans[0].onmouseout = function(){
changeColor(this,'#333','#f5f5f5')
}
下面是一个使用桥接模式多元化对象的例子:
//多维变量类
//运动单元
function Speed(x,y){
this.x = x;
this.y = y;
}
Speed.prototype.run = function(){
console.log('运动起来');
}
//着色单元
function Color(cl){
this.color = cl;
}
Color.prototype.draw = function(){
console.log('绘制色彩');
}
//变形单元
function Shape(sp){
this.shape = sp;
}
Shape.prototype.change = function(){
console.log('改变形状');
}
//说话单元
function Speek(wd){
this.word = wd;
}
Speek.prototype.say = function(){
consle.log('书写字体');
}
//球类
function Ball(x,y,c){
//实现运动单元
this.speed = new Speed(x,y);
//实现着色单元
this.color = new Color(c);
}
Ball.prototype.init = function(){
//实现运动
this.speed.run();
//实现着色
this.color.draw();
}
//人物类
function People(x,y,f){
this.speed = new Speed(x,y);
this.font = new Speek(f);
}
6,组合模式
组合模式,又称部分-整体模式,将对象组合成树形结构以表示"部分整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
一个使用组合模式的例子:
/***
*子组件容器
***/
var News = function(){
this.children = [];
//当前组件元素
this.element = null;
}
News.prototype = {
init:function(){
throw new Error("请重写你的方法");
},
add:function(){
throw new Error("请重写你的方法");
},
getElement:function(){
throw new Error("请重写你的方法");
}
}
/***
*容器构造类函数
***/
var Container = function(id,parent){
//构造函数继承父类
News.call(this);
//模块id
this.id = id;
//模块的父容器
this.parent = parent;
//构建方法
this.init();
}
//寄生式继承父类原型方法
inheritPrototype(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();
}
inheritPrototype(Item,News);
Item.prototype.init = function(){
this.element = document.createElement('li');
this.element.className = this.classname;
}
Item.prototype.add = function(child){
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
}
Item.prototype.getElement = function(){
return this.element;
}
/***
*新闻组合体类
***/
var NewsGroup = function(classname){
News.call(this);
this.classname = classname || ' ';
this.init();
}
inheritPrototype(NewsGroup,News);
NewsGroup.prototype.init = function(){
this.element = document.createElement('div');
this.element.className = this.classname;
}
NewsGroup.prototype.add = function(child){
//在子容器中插入子元素
this.children.push(child);
this.element.appendChild(child.getElement());
return this;
}
NewsGroup.prototype.getElement = function(){
return this.element;
}
/***
*创建新闻基类
***/
//创建图片新闻类
var ImageNews = function(url,href,classname){
News.call(this);
this.url = url || ' ';
this.href = href || '#';
this.classname = classname || 'normal';
this.init();
}
inheritPrototype(ImageNews,News);
ImageNews.prototype.init = function(){
this.element = document.createElement('a');
var img = new Image();
img.src = this.url;
this.element.appendChild(img);
this.element.className = 'image-news ' + this.classname;
this.element.href = this.href;
}
ImageNews.prototype.add = function(){}
ImageNews.prototype.getElement = function(){
return this.element;
}
//图标基类
var IconNews = function(text,href,type){
News.call(this);
this.text = text || ' ';
this.href = href || '#';
this.type = type || 'video';
this.init();
}
inheritPrototype(IconNews,News);
IconNews.prototype.init = function(){
this.element = document.createElement('a');
this.element.innerHTML = this.text;
this.element.href = this.href;
this.element.className = 'icon ' + this.type;
}
IconNews.prototype.add = function(){}
IconNews.prototype.getElement = function(){
return this.element;
}
//文字新闻类
var EasyNews = function(text,href){
News.call(this);
this.text = text || ' ';
this.href = href || '#';
this.init();
}
inheritPrototype(EasyNews,News);
EasyNews.prototype.init = function(){
this.element = document.createElement('a');
this.element.innerHTML = this.text
this.element.href = this.href;
this.element.className = 'text';
}
EasyNews.prototype.add = function(){}
EasyNews.prototype.getElement = function(){
return this.element;
}
//TypeNews
var TypeNews = function(text,href,type,pos){
News.call(this);
this.text = text || ' ';
this.href = href || '#';
this.type = type || ' ';
this.pos = pos || 'left';
this.init();
}
inheritPrototype(TypeNews,News);
TypeNews.prototype.init = function(){
this.element = document.createElement('a');
if(this.pos === 'left'){
this.element.innerHTML = '[' + this.type + '] ' + this.text;
}else{
this.element.innerHTML = this.text + ' [' + this.type + ']';
}
this.element.href = this.href;
this.element.className = 'text';
}
TypeNews.prototype.add = function(){}
TypeNews.prototype.getElement = function(){
return this.element;
}
/***
*实例新闻模块
***/
var news1 = function(id){
this.id = id;
this.element = document.createElement('a');
this.element.id = id;
this.getElement = function(){
return this.element
}
}
var news1 = new Container('news',document.body);
news1.add(
new Item('normal').add(
new IconNews('梅西不拿金球也伟大','#','video')
)
).add(
new Item('normal').add(
new IconNews('保护强国强队用意明显','#','live')
)
).add(
new Item('normal').add(
new NewsGroup('has-imp').add(
new ImageNews('img/1.jpg','#','small')
).add(
new EasyNews('从240斤胖子成功变型男','#')
).add(
new EasyNews('五大雷人跑步机','#')
)
)
).add(
new Item('normal').add(
new TypeNews('AK47不愿为费城打球','#','NBA','left')
)
).add(
new Item('normal').add(
new TypeNews('火炮彪6三分创新高','#','CBA','right')
)
).show();
7,享元模式
享元模式运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。
享元模式主要对数据,方法共享分离,讲数据和方法分成内部数据,内部方法和外部数据,外部方法。