原文出自于本人个人博客网站:https://www.dzyong.com(欢迎访问)
转载请注明来源: 邓占勇的个人博客 - 《JavaScript设计模式(6)—— 结构型设计模式【2】》
本文链接地址: https://www.dzyong.com/#/ViewArticle/91
设计模式系列博客:JavaScript设计模式系列目录(持续更新中)
在上一篇博客中介绍了结构型设计模式的前4种:外观模式、适配器模式、代理模式和装饰者模式。
接下来介绍剩下的3种结构型设计模式:桥接模式、组合模式和享元模式。
桥接模式
在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。
看下面这个案例,当鼠标滑过时,对于项目来说需要使整个的样式改变,对于消息来说,只需要对数字的样式改变即可。
HTML部分如下:
<span>项目1</span>
<span>消息<strong>9</strong></span>
如果是传统写法一般是这样写:
let spans = document.getElementsByTagName('span')
spans[0].onmouseover = function(){
this.style.color = 'red'
this.style.background = 'pink'
}
spans[0].onmouseout = function(){
this.style.color = 'black'
this.style.background = '#fff'
}
spans[1].onmouseover = function(){
this.getElementsByTagName('strong')[0].style.color = 'red'
this.getElementsByTagName('strong')[0].style.background = 'pink'
}
spans[1].onmouseout = function(){
this.getElementsByTagName('strong')[0].style.color = 'black'
this.getElementsByTagName('strong')[0].style.background = '#fff'
这看起来似乎显得很繁琐,存在许多共同的部分需要重复的编写。我可以对其进行改进。
首先可以抽象提取出他们的共同部分,如改变样式:
/*提取共同点*/
let changecolor = function(dom, color, bg){
dom.style.color = color
dom.style.background = bg
}
接下来就是时事件绑定,但是需要明白一点,仅仅知道元素事件绑定与抽象提取的设置样式方法是不够的,你需要一个方法将他们链接起来,这个方法就是桥接方法,这种模式就是桥接模式。
let spans = document.getElementsByTagName('span')
spans[0].onmouseover = function(){
changecolor(this, 'red', 'pink')
}
spans[0].onmouseout = function(){
changecolor(this, 'black', '#fff')
}
spans[1].onmouseover = function(){
changecolor(this.getElementsByTagName('strong')[0], 'red', 'pink')
}
spans[1].onmouseout = function(){
changecolor(this.getElementsByTagName('strong')[0], 'black', '#fff')
}
不过桥接模式的强大之处不仅仅在此,甚至对于多维的变化也同样使用。
比如我们书写一个canvas跑步游戏的时候,对于游戏中的人、精灵、小球等一系列的实物都有动作单元,而他们的动作实现起来的方式又都是统一的,比如人、精灵和球的运动其实就是位置坐标x和y的变化,球的颜色与精灵的色彩的绘制方式都相似等。这种我们可以将这些多维变化部分,提取出来作为一个抽象运动单元进行保存,而当我们创建实体时,将需要的每个抽象动作作为单元通过桥接,链接在一起运作。这样他们之间不会相互影响并且该方式降低了他们之间的耦合。
//运动单元
let Speed = function(x, y){
this.x = x
this.y = y
}
Speed.prototype.run = function(){
console.log('跑起来')
}
//着色单元
let Color = function(c){
this.color = c
}
Color.prototype.draw = function(){
console.log('上色')
}
//变形单元
let Shape = function(sp){
this.shape = sp
}
Shape.prototype.change = function(){
console.log('改变形状')
}
//说话单元
let Speek = function(wd){
this.word = wd
}
Speek.prototype.say = function(){
console.log('说话')
}
//创建小球类
let Ball = function(x, y, c){
this.speed = new Speed(x, y)
this.color = new Color(c)
}
//初始化
Ball.prototype.init = function(){
this.speed.run()
this.color.draw()
}
let b = new Ball(2, 2, 'red')
b.init()
组合模式
又称部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
享元模式
运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。
例子:有一个新闻模块,新闻数量较多,因此需要进行分页展示。一种简便的方法就是先请求获取所有的新闻数据,并全部添加到一个div盒子中,根据所选择的页码不同,对相应的新闻进行显示隐藏。
上面这种做法存在一个很严重的问题,所有的新闻模块结构是相同的,只是内容不同。如果一次性把所有的新闻同时插入页面中的操作会造成多余的开销,在低版本的IE浏览器中会严重影响性能。
对于这种情况就可以采用享元模式,主要对其数据、方法共享分离,它将数据和方法分成内部数据、内部方法和外部数据、外部方法。内部数据与内部方法指的是相似或者共有的数据和方法,所以这一部分要提取出来减少开销,以提高性能。
let Flyweight = function(){
//已创建的元素
var created = []
//创建一个新闻包装容器
function create(){
var dom = document.createElement('div')
document.getElementById('container').appendChild(dom)
created.push(dom)
return dom
}
return {
//获取新闻元素方法
getDiv: function(){
//如果已创建的元素小于当前页数总个数,则创建
if(created.length < 5)
return create()
else{
//获取第一个元素,并插入最后
var div = created.shift()
created.push(div)
return div
}
}
}
}
内部数据和方法提取出来之后就要实现外部数据和外部方法,外部数据就是要显示的所有新闻内容,因为每个内容不一样,所以他们不能被共享。
let article = ['第1条内容','第2条内容','第3条内容','第4条内容','第5条内容','第6条内容','第7条内容','第8条内容','第9条内容','第10条内容','第11条内容','第12条内容','第13条内容','第14条内容','第15条内容','第16条内容',]
let paper = 0,
num = 5,
len = article.length
//添加5条新闻
for (let i = 0; i < 5; i++) {
if(article[i])
Flyweight().getDiv().innerHTML = article[i]
}
//下一页按钮绑定事件
document.getElementById('next').onclick = function(){
//如果新闻内容不足5条则返回
if(article.length < 5)
return
document.getElementById('container').innerHTML = ''
var n = ++paper * num % len, //获取当前页的第一条新闻索引
j = 0
//插入新闻
for (;j < 5;j++) {
//如果存在n + j 条则插入
if(article[n + j])
Flyweight().getDiv().innerHTML = article[n + j]
//否则插入其实位置n + j - len条
else if(article[n + j - len])
Flyweight().getDiv().innerHTML = article[n + j - len]
//如果不存在则插入空字符串
else
Flyweight().getDiv().innerHTML = ''
}
}