JavaScript设计模式之结构型设计模式

继上篇创建型设计模式之后,此篇博文主要记录结构型设计模式的学习过程,如果在某些地方表述不正确的,还希望大家能够指出,共同进步~

结构型设计模式

结构型设计模式是一类关注于如何将类或对象组合成更大、更复杂的结构的设计模式,主要应用于对象的组合。包括外观模式适配器模式代理模式装饰者模式桥接模式组合模式以及享元模式

外观模式

外观模式,为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更加容易。

外观模式

  • 外观模式简化底层接口复杂性,比如可以用来简化我们事件的绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function addEvent(dom, type, fn){
    if(dom.addEventListener){
    dom.addEventListener(type, fn, false);
    }
    else if(dom.attachEvent){
    dom.attachEvent('on' + type, fn);
    }
    else{
    dom['on' + type] = fn;
    }
    }
  • 可以实现小型代码库,通过使用外观模式来封装多个功能,简化底层操作方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var A = {
    g: function(id){
    return document.getElementById(id);
    },
    css: function(id, key, value){
    document.getElementById(id).style[key] = value;
    },
    attr: function(id, key, value){
    document.getElementById(id)[key] = value;
    },
    html: function(id, html){
    document.getElementById(id).innerHTML = html;
    },
    on: function(id, type, fn){
    document.getElementById(id)['on' + type] = fn;
    }
    }
    // Test
    A.css('box', 'background', 'red');

当一个复杂的系统提供一系列复杂的接口方法时,为系统的管理方便会造成接口方法的使用极其复杂,我们可以通过使用外观模式封装隐藏其复杂性,并简化其使用。

适配器模式

适配器模式,将一个类(对象)的接口(方法或者属性)转化成另外一个接口,以满足用户需求。

  • 适配异类框架

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //定义框架
    var A = A || {};
    A.g = function(id){
    return document.getElementById(id);
    }
    A.on = function(id, type, fn){
    var dom = typeof id === 'string'? this.g(id) : id;
    if(dom.addEventListener){
    dom.addEventListener(type, fn, false);
    }
    else if(dom.attachEvent){
    dom.attachEvent('on' + type, fn);
    }
    else{
    dom['on' + type] = fn;
    }
    }

    //适配JQuery
    A.g = function(id){
    return $(id).get(0);
    }
    A.on = function(id, type, fn){
    var dom = typeof id === 'string'? $('#' + id) : $(id);
    dom.on(type, fn);
    }
  • 参数适配器,用适配器来适配传入的参数对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 适配参数
    function doSomeThing(obj){
    var _adapter = {
    name: 'qiuqiu',
    title: 'js',
    age: '20',
    color: 'pink',
    size: 100,
    prize: 50
    }
    for(var i in _adapter){
    _adapter[i] = obj[i] || _adapter[i];
    }
    }
  • 数据适配,将数组转换为对象形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 数据适配
    function arrToObjAdapter(arr){
    return {
    name: arr[0],
    type: arr[1],
    title: arr[2],
    data: arr[3]
    }
    }

在JavaScipt中,适配器模式不仅仅可以适配两个类接口不兼容的问题,还可以用于适配两个代码库,适配前后端数据等

代理模式

代理模式,由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。
proxy
在JavaScript中,代理模式往往用于解决跨域请求问题。

装饰者模式

装饰者模式,在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 装饰者模式
// 装饰者
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;
}
}

// Test
decorator('tel-input', function(){
document.getElementById('tel_demo_text').style.display = 'none';
})

适配器方法是对原有对象适配,添加的方法与原有方法功能大致相似,装饰者模式提供的方法与原来的方法功能是有一定区别的,在装饰者模式中,不需要了解对象原有的功能就可以对功能进行拓展。
装饰者模式对对象的拓展是一种良性拓展,不用了解其具体实现,只是在外部进行了一次封装,这又是对原有功能完整性的一种保护。

桥接模式

桥接模式,在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。
bridge-1
桥接模式
多维度:
桥接模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 桥接模式
// 运动单元
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 Speak(wd){
this.word = wd;
}
Speak.prototype.say = function(){
console.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.speak = new Speak(f);
}
People.prototype.init = function(){
this.speed.run();
this.speak.say();
}

// Test
var ball = new Ball(2, 5, 'red');
ball.init();

bridge
桥接模式先抽象提取共用部分,然后将实现与抽象通过桥接方法链接在一起,来实现解耦的作用。如我们创建实体Ball时,将需要的每个抽象动作单元SpeedColor通过桥接,链接在一起运作,这样它们之间不会相互影响并且该方式降低了它们之间的耦合。
桥接模式最主要的特点是将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化。

组合模式

组合模式,又称为部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性。
组合模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// 组合模式
// 寄生组合式继承:寄生式继承+构造函数继承
function inheritObject(o){
// 声明一个过渡对象
function F(){};
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的实例,该实例的原型继承了父对象
return new F();
}

// 处理的不是对象,而是类的原型
function inheritPrototype(subClass, superClass){
// 复制一份父类的原型副本保存在变量中
var p = new inheritObject(superClass.prototype);
// 修正因为重写子类类型导致子类的constructor属性被修改
p.constructor = subClass;
// 设置子类的原型
subClass.prototype = p;
}
// 虚拟父类
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(iid, parent){
News.call(this);
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 ImageNews = function (url, href, classname) {
News.call(this);
this.url = url || '';
this.href = href || '#';
this.classname = classname || '';
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.className = 'icon' + this.type;
this.element.href = this.href;
}
IconNews.prototype.add = function(){}
IconNews.prototype.getElement = function(){
return this.element;
}

// Test
var news1 = new Container('news', document.body);
news1.add(
new Item('normal').add(
new IconNews('lalala', '#', 'video')
)
).add(
new Item('normal').add(
new IconNews('aiaia', '#', 'live')
)
).add(
new Item('normal').add(
new ImageNews('img/test1.png', '#', 'small')
)
)

组合模式的约束要求是接口的统一,上面的例子中,我们让所有的新闻都继承了一个新闻虚拟父类News,其中在虚拟类的构造函数中定义了两个特权变量,是因为后面的所有继承子类都要声明这两个变量,为了简化子类我们也可以将这些共有的变量提前声明在父类中,相当于Java语言中protected关键字的作用。
组合模式能够给我们提供一个清晰的组成结构,组合对象类通过继承同一个父类使其具有统一的方法,这样也方便了我们统一管理与使用。

享元模式

享元模式,运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 享元模式
// 通用享元类
var FlyWeight = {
moveX: function(x){
this.x = x;
},
moveY: function(y){
this.y = y;
}
}

var Player = function(x, y, c){
this.x = x;
this.y = y;
this.c = c;
}
Player.prototype = FlyWeight;
Player.prototype.changeC = function(c){
this.c = c;
}

var Spirit = function(x, y, r){
this.x = x;
this.y = y;
this.r = r;
}
Spirit.prototype = FlyWeight;
Spirit.prototype.changeR = function(r){
this.r = r;
}

// Test
var player = new Player(5, 6, 'red');
console.log(player);
player.moveX(6);
player.moveY(8);
player.changeC('green');
console.log(player)

var spirit = new Spirit(2, 3, 4);
console.log(spirit);
spirit.moveX(3);
spirit.moveY(4);
spirit.changeR(5);
console.log(spirit)

flyWeight
我们将人物类Player以及精灵类Spirit中的内部方法(移动方法)提取出来,实现公有,减少其它类重写时造成的不必要的开销。
享元模式主要是对其数据、方法共享分离,它将数据和方法分成内部数据内部方法外部数据外部方法。内部方法与内部数据指的是相似或者共有的数据和方法。

总结

JavaScript设计模式之结构型设计模式到此也就告一段落了,因为其中有部分设计模式之前因为时间不足未曾深入学习,所以可能有些理解的不够透彻,如果有存在描述不当的地方,欢迎大家指出~共同进步~

下一篇我将记录JavaScript设计模式中行为型设计模式的学习过程~加油加油~

本文标题:JavaScript设计模式之结构型设计模式

文章作者:萌萌哒的邱邱邱邱

发布时间:2018年02月06日 - 13:02

最后更新:2018年02月10日 - 11:02

原始链接:https://qiuruolin.github.io/2018/02/06/js-structural-pattern/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------
感谢您的支持