目录索引
JS设计模式:结构型
(一)外观模式(Facade)
为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易。在Javascript中有时也会用于对底层结构兼容性做统一封装来简化用户使用。
关键词:简化封装
外观模式是对接口方法的外层包装,以供上层代码调用,不需了解对象内部实现。多用于代码库封装功能。
- 简化底层接口复杂性
- 解决浏览器兼容性问题
示例:
// 封装新代码库功能,简化底层代码
var myCode = {
// 设置元素的属性
css: function(id, key, value) {
document.getElementById(id).style[key] = value;
}
}
// 简化后调用
myCode.css('myId', 'color', '#333');
// 封装绑定事件兼容不同浏览器
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;
}
}
(二)适配器模式(Adapter)
将一个类(对象)的接口(方法或属性)转化成另外一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决。
关键词:重组封装
Javascript中的适配器,更多应用在对象之间,为了使对象可用,通常会将对象拆分并重新包装。
适配器模式对原有对象进行拓展,是对对象内部结构的重组,需了解其自身结构。
- 适配异类框架(适配两个代码库)
- 参数适配器(考虑参数顺序及完整性,写插件时对于参数的配置)
- 数据适配器(服务端数据适配,解决前后端的数据依赖)
示例:
// 参数适配
function myFn(obj) {
var _adapter = {
argument1: '参数1及默认值',
argument2: '参数2及默认值'
};
for (var i in _adapter) {
_adapter[i] = obj[i] || _adapter[i];
}
// do things
}
// 经常使用的还是参数适配器和数据适配器
// eg. 如上写一个参数适配插件配置
// eg. 如商品规格在前端展示列表与后端返回数据不同,需要进行数据适配器处理再渲染。
外观模式与适配器模式的区别:二者都解决了对象之间的耦合度,但适配器模式需要了解适配对象的内部结构,而外观模式不需要。
(三)代理模式(Proxy)
由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。
一般代理对象有 img
script
link
,主要涉及跨域的一些知识。
(四)装饰者模式(Decorator)
在不改变原对象的基础上,通过对其进行包装拓展(添加属性或方法)使原有对象可以满足用户的更复杂需求。
关键词:封装拓展
装饰者模式对原有对象进行拓展,是在外部进行了一次封装拓展,不需了解对象内部实现,保护原有功能完整性。
示例:
// 场景:为输入框增加新交互需求功能
var phoneInput = document.getElementById('phone_input');
phoneInput.onclick = function() {
console.log('phoneInput原绑定事件');
}
// 装饰者
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('phoneInput', function(){
console.log('给phoneInput绑定新事件');
})
适配器模式与装饰者模式的区别:适配器模式是对原有对象适配,添加的方法与原有方法功能上大致相似。但是装饰者提供的方法与原来的方法功能项是有一定区别的。再有,使用适配器时我们新的方法是要调用原来的方法,在装饰者则不需要了解对象原有的功能,并且对象原有的方法照样可以原封不动地使用。
(五)桥接模式(Bridge)
在系统沿着多个维度变化的同时,又不增加其复杂度并已达到解耦。
关键词:多维解耦
桥接模式是先抽象提取共用部分,然后将实现与抽象通过桥接方式链接在一起,来实现解耦的作用,而对于多维变化也同样适用。
桥接模式最主要的特点即是将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化。体现了面向对象对拓展的开放及修改的关闭原则。
示例:
// 抽象类
function AbstractClassSpeed(x, y){
this.x = x;
this.y = y;
}
AbstractClassSpeed.prototype.run = function() {
console.log('AbstractClassSpeed run')
}
function AbstractClassSpeak(word){
this.word = word;
}
AbstractClassSpeak.prototype.say = function() {
console.log('AbstractClassSpeak say')
}
// 桥接器
function BridgeClassPeople(x, y, word){
this.speed = new AbstractClassSpeed(x, y);
this.speak = new AbstractClassSpeak(word);
}
BridgeClassPeople.prototype.init = function() {
this.speed.run();
this.speak.say();
}
// 实例
var people = new BridgeClassPeople(10, 20, 'I say a word!');
people.init();
(六)组合模式(Composite)
又称部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
关键词:多层次组合
表单
组合对象类通过继承同一个父类使其具有统一的方法。有时这也是对数据的分级式处理。
(七)享元模式(Flyweight)
运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。
关键词:共享分离
分页
享元模式通过合理提取共有的数据和方法,提高程序的执行效率与系统的性能。
一般用于大型系统开发。