什么是设计模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
比较权威的是GOF(四人帮,全拼 Gang of Four)出版的《设计模式》,该书首次提到了软件开发中设计模式的概念。
设计模式类型
设计模式中比较被认可的目前大致是23种,这23种模式可以分为三类
创建型模式(Creational Patterns):对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式(Structural Patterns):把类或对象结合在一起形成一个更大的结构。
行为型模式(Behavioral Patterns):类和对象如何交互,及划分责任和算法。
四个要素
模式名称:顾名思义,就是一个助记名,用一两个词来描述模式的问题、解决方案和效果。
问题:描述问题存在的前因后果
解决方案:描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
效果:描述了模式应用的效果及使用模式应权衡的问题。
六大原则
开-闭原则:对扩展开放,对修改关闭。
里氏代换原则:如果调用的是父类的话,那么换成子类也完全可以运行。
合成复用原则:就是说要少用继承,多用合成关系来实现。
依赖倒转原则:要针对接口编程,而不是针对实现编程。
接口隔离原则:单一接口。
迪米特法则(也称为最小知识原则):一个软件实体应当尽可能的少与其他实体发生相互作用。
各个模式介绍以及详细用法
结构型模式:
1.代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
应用场景:图片懒加载(先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。)
var fack=a;
var real=b;//这个方法只负责创建img标签,对外暴露设置img的src接口
function myImage(){var image=document.createElement('img');document.body.append(image);return {set:function(src){image.src=src;}}
}
//在body里面异步加载图片,传入图片地址参数,和本地loadding图片
var myImg=myImage();
var proxyImage=function(){var img=new Image();img.onload=function(){console.log(this);myImg.set(this.src);}return {set:function(loadimg,src){img.src=src;myImg.set(loadimg);}}
}var proxy=proxyImage();
proxy.set(fack,real);
2.装饰模式
定义:通俗易懂点讲就是动态的给对象在原本上增加方法,
应用场景:把糖做成圆球状,它即是圆球又是糖
将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。
var Plan1 = {fire: function () {console.log('发射普通的子弹');}
};var missileDecorator= function () {console.log('发射导弹!');
};var fire = Plan1.fire;Plan1.fire=function () {fire();missileDecorator();
};Plan1.fire();
3.适配器模式
定义:将一个类的接口转换成客户希望的另外一个接口。使原本不能一起工作的接口可以一起工作。
应用场景:例如金丝猴跟猩猩都是灵长类,但是金丝猴树上比较灵活,那么就给猩猩加个功能使得它也能像金丝猴一样。
var googleMap = {show:function(){console.log('开始渲染谷歌地图');}};var baiduMap = {display:function(){console.log('开始渲染百度地图');}};var baiduMapAdapter = {show:function(){return baiduMap.display();}};var renderMap = function(map){if(map.show instanceof Function){map.show();}};renderMap(googleMap); //输出:开始渲染谷歌地图renderMap(baiduMapAdapter); //输出:开始渲染百度地图
4.外观模式
定义:为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用。
应用场景:外观模式在JS中常常用于解决浏览器兼容性问题。
function addEvent(dom, type, fn) {if (dom.addEventListener) { // 支持DOM2级事件处理方法的浏览器dom.addEventListener(type, fn, false)} else if (dom.attachEvent) { // 不支持DOM2级但支持attachEventdom.attachEvent('on' + type, fn)} else {dom['on' + type] = fn // 都不支持的浏览器}
}const myInput = document.getElementById('myinput')
addEvent(myInput, 'click', function() {console.log('绑定 click 事件')})
5.桥接模式
定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
应用场景:,比如提取运动,着色,说话模块,球类可以具有运动和着色模块,人类可以具有运动和说话模块,这样可以实现模块的快速组装,不仅仅是实现与抽象部分相分离了,而是更进一步功能与抽象相分离,进而灵活地创建对象。
class Speed { // 运动模块constructor(x, y) {this.x = xthis.y = y}run() { console.log(`运动起来 ${this.x} + ${this.y}`) }
}class Color { // 着色模块constructor(cl) {this.color = cl}draw() { console.log(`绘制颜色 ${this.color}`) }
}class Speak {constructor(wd) {this.word = wd}say() { console.log(`说话 ${this.word}`) }
}class Ball { // 创建球类,可以着色和运动constructor(x, y, cl) {this.speed = new Speed(x, y)this.color = new Color(cl)}init() {this.speed.run()this.color.draw()}
}class Man { // 人类,可以运动和说话constructor(x, y, wd) {this.speed = new Speed(x, y)this.speak = new Speak(wd)}init() {this.speed.run()this.speak.say()}
}const man = new Man(1, 2, 'hehe?')
man.init() // 运动起来 1 + 2 说话 hehe?
6.组合模式
定义:将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
应用场景:回家我要开门、打开电脑、开音乐,组合起来一并执行。
class GoHome{init(){console.log("到家了,开门");}}class OpenComputer{init(){console.log("开电脑");}}class OpenXiaoAi{init(){console.log("开音乐");}}// 组合器,用来组合功能class Comb{constructor(){// 准备容器,用来防止将来组合起来的功能this.skills = [];}// 用来组合的功能,接收要组合的对象add(task){// 向容器中填入,将来准备批量使用的对象this.skills.push(task);}// 用来批量执行的功能action(){// 拿到容器中所有的对象,才能批量执行,forEach拿到每个对象返回到箭头函数里面this.skills.forEach( val => {val.init();//val相当于this.skill[i]} );}}// 创建一个组合器var c = new Comb();// 提前将,将来要批量操作的对象,组合起来c.add( new GoHome() );c.add( new OpenComputer() );c.add( new OpenXiaoAi() );// 等待何时的时机,执行组合器的启动功能c.action();// 在内部,会自动执行所有已经组合起来的对象的功能
7.享元模式
定义:运用共享技术,有效地支持大量的细粒度的对象,以避免对象之间拥有相同内容而造成多余的性能开销。
应用场景:现有一个新闻列表的页面,每页显示5条新闻。当点击页面中的“下一页”时,即实现新闻列表的翻页功能。
// 享元模式
var FlyWeight = function() {// 已创建的元素var created = [];// 创建一个新闻容器function create() {var dom = document.createElement('div'); // 一条新闻的容器document.getElementById('page').appendChild(dom);// 缓存新创建的元素created.push(dom);return dom;}return {// 获取新闻元素getDiv: function() {// 每页显示5条新闻if (created.length < 5) {return create();} else {// 获取第一个元素,并插入至最后var div = created.shift();created.push(div);return div;}}}
}();
// 使用上述封装的享元模式,实现需求
var page = 0, num = 5, len = article.length;
// 添加5条新闻
for (var i = 0; i<5; i++) {if (article[i]) {// 只更新内容,不重新创建容器FlyWeight.getDiv().innerHTML = article[i];}
}
// 给“下一页”按钮绑定翻页事件
document.getElementById('next-page').onclick = function() {if (article.length < 5) {return;}// 获取当前页的第一条新闻索引var n = ++page * num % len;// 插入5条新闻for (var j = 0; j<5; j++) {if (article[n+j]) {FlyWeight.getDiv().innerHTML = article[n+j];} else if (article[n+j-len]) {FlyWeight.getDiv().innerHTML = article[n+j-len];} else {FlyWeight.getDiv().innerHTML = "";}}
}
创建型模式:
1.单例模式
定义:多次执行,只有一个对象。
应用场景:如果需要多次创建同一个对象,完成同一个事件,肯定会多次new,真的会产生多个对象,但没必要。修改成,多次执行,只有一个对象。
需要一个构造函数
function Person(){}封装一个单例模式的调用方式
var f = (function(){var instance;return function(){if(!instance){instance = new Person();}return instance;}})()
因为f相当于接收匿名函数返回来的单例模式
var p1 = f();
var p2 = f();
console.log(p1 === p2);
2.工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。
应用场景:假如每次添加一种新的产品,都要去更改工厂内部,工厂方法模式就是将这个大厂拆分出各个小厂,每次添加新的产品让小厂去生产,大厂负责指挥就好了。
//邮件
var Factory = function (type,content) {if(this instanceof Factory){var s = new this[type](content);return s;}else {return new Factory(type,content);}
}
Factory.prototype = {java:function (content) {},javaScript1:function (content) {},ui:function (content) {},php:function (content) {}
};
3.抽象工厂模式
定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
应用场景:有个电脑店的门店 现在电脑店火了 又开了一个电脑店 然后这两个电脑店一个是联想的 一个是戴尔的
let agency = function(subType, superType) {//判断抽象工厂中是否有该抽象类if(typeof agency[superType] === 'function') {function F() {};//继承父类属性和方法F.prototype = new agency[superType] ();//将子类的constructor指向子类subType.constructor = subType;//子类原型继承父类subType.prototype = new F();} else {throw new Error('抽象类不存在!')}
}
4.建造者模式
定义:可以将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。实际,就是一个指挥者,一个建造者
应用场景:建房子
//白富美发送一个请求 要建房子
//包工头接受这个请求,并招募工人建房
//工人完成建造房子的人//1.产出的是房子
//2.baogongtou调用工人进行开工,而且他要清除工人们具体的某一个大项
//3.工人是盖房子 工人可以建卧室、客厅、厨房
//4.包工头只是一个接口,他不干活,他对外说我能建房子
function Fangzi(){
this.woshi='';
this.keting='';
this.chufang='';
}
function Baogongtou(){
this.jianfangzi=function(gongren){
gongren.jian_woshi();
gongren.jian_keting();
gongren.jian_chufang();
}
}
function Gongren(){
this.jian_woshi=function(){
console.log('卧室已经建好了');
}
this.jian_keting=function(){
console.log('客厅已经建好了');
}
this.jian_chufang=function(){
console.log('厨房已经建好了');
}
this.jiaogong=function(){
var _fangzi=new Fangzi();_fangzi.woshi='ok';
_fangzi.keting='ok';
_fangzi.chufang='ok';
return _fangzi;
}
}
var gongren=new Gongren;
var baogongtou=new Baogongtou;
baogongtou.jianfangzi(gongren);
var myfangzi=gongren.jiaogong();
console.log(myfangzi);
5.原型模式
定义:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
应用场景:人有多个技能,会唱歌跳舞打代码,但出发点一样都是人。
function Fn() {this.x = 100;this.sum = function() {}
};Fn.prototype.getX = function() {console.log(this.x);
};
//再在公有链上增加一个sum
Fn.prototype.sum = function() {}var f1 = new Fn;
var f2 = new Fn;
console.log(Fn.prototype.constructor === Fn);
行为型模式:
1.策略模式
定义:给出多个计划,当将来发生某种状态时,执行对应的计划
应用场景:轮播图、选项卡之类
var obj = {"A": function(salary) {return salary * 4;},"B" : function(salary) {return salary * 3;},"C" : function(salary) {return salary * 2;}
};
var calculateBouns =function(level,salary) {return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000
2.观察者模式
定义:以观察者的角度,发现对应的状况,处理问题
发布者:发布消息
会随时更新自身的信息或状态
订阅者:接收信息
接收到发布者发布的信息,从而做出对应的改变或执行
应用场景:很方便的实现简单的广播通信,实现一对多的对应关系
订阅者可以随时加入或离开。
function Stu(n){this.name = n;this.type = function(){if(Math.random() > 0.5){return "学习";}else{return "睡觉";}}}function Teac(n){this.name = n;this.listen = function(t,sn){if(t == "学习"){console.log(`${sn}是好孩子`);}else{console.log(`给${sn}一巴掌`);}}}function Teac2(n){this.name = n;this.listen = function(t,sn){if(t == "学习"){console.log(`嗯嗯...点头离开`);}else{console.log(`把${sn}揪起来,叫家长`);}}}var s = new Stu("张三");var t = s.type();var t1 = new Teac("班主任");t1.listen(t, s.name);var t2 = new Teac2("教导主任");t2.listen(t, s.name);
3.解释器模式
定义:对于一种语言,给出其文法表示形式,并定义一种解释器,通过使用这种解释器来解释语言中定义的句子。
应用场景:统计页面中点击事件触发元素在页面所处的路径。
<div id="container"><div><div><ul><li><span id="span1"></span></li><li><span id="span2"></span></li></ul></div></div><div><div><ul><li><span id="span5"></span></li><li><span id="span6"></span></li></ul></div></div></div><script>// xPath解释器var Interpreter = (function () {// 获取兄弟元素名称function getSulingName(node) {if (node.previousSibling) {var name = '',count = 1,nodeName = node.nodeName,sibling = node.previousSiblingwhile (sibling) {if (sibling.nodeType == 1 && sibling.nodeType === node.nodeType && sibling.nodeName) {// 如果节点名称和前一个兄弟元素名称相同if (nodeName == sibling.nodeName) {name += ++count} else {count = 1name += '|' + sibling.nodeName.toUpperCase()}}sibling = sibling.previousSibling}return name} else {return ''}}return function (node, wrap) {var path = [],wrap = wrap || documentif (node == wrap) {if (wrap.nodeType == 1) {path.push(wrap.nodeName.toUpperCase())}return path}if (node.parentNode !== wrap) {path = arguments.callee(node.parentNode, wrap)}else {if (wrap.nodeType == 1) {path.push(wrap.nodeName.toUpperCase())}}var sublingsNames = getSulingName(node)if (node.nodeType == 1) {path.push(node.nodeName.toUpperCase() + sublingsNames)}return path}})()var path = Interpreter(document.getElementById('span5'))console.log(path)</script>
4.模板方法模式
定义:一种只需使用继承就可以实现的非常简单的模式。
应用场景:煮水去泡茶或是冲咖啡。
const Drinks = function() {}Drinks.prototype.firstStep = function() {console.log('烧开水')
}Drinks.prototype.secondStep = function() {}Drinks.prototype.thirdStep = function() {console.log('倒入杯子')
}Drinks.prototype.fourthStep = function() {}Drinks.prototype.init = function() { // 模板方法模式核心:在父类上定义好执行算法this.firstStep()this.secondStep()this.thirdStep()this.fourthStep()
}const Tea = function() {}Tea.prototype = new DrinksTea.prototype.secondStep = function() {console.log('浸泡茶叶')
}Tea.prototype.fourthStep = function() {console.log('加柠檬')
}const Coffee = function() {}Coffee.prototype = new DrinksCoffee.prototype.secondStep = function() {console.log('冲泡咖啡')
}Coffee.prototype.fourthStep = function() {console.log('加糖')
}const tea = new Tea()
tea.init()// 烧开水
// 浸泡茶叶
// 倒入杯子
// 加柠檬const coffee = new Coffee()
coffee.init()// 烧开水
// 冲泡咖啡
// 倒入杯子
// 加糖
5.迭代子模式
定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示
应用场景:jQuery里一个非常有名的迭代器就是$.each方法,通过each我们可以传入额外的function,然后来对所有的item项进行迭代操作
$.each(['dudu', 'dudu', '酸奶小妹', '那个MM'], function (index, value) {console.log(index + ': ' + value);
});
//或者
$('li').each(function (index) {console.log(index + ': ' + $(this).text());
});
6.责任链模式
定义:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
应用场景:用责任链模式获取文件上传对象。
function getActiveUploadObj() {try{return new ActiveObject("TXFTNActiveX.FTNUpload"); // IE上传控件}catch(e) {return "nextSuccessor";}
}function getFlashUploadObj() {if(supportFlash().f === 1) { // supportFlash见《JavaScript设计模式--迭代器模式》var str = '<object type="application/x-shockwave-flash"></object>';return $(str).appendTo($("body"));}return "nextSuccessor";
}function getFormUploadObj() {var str = '<input name="file" type="file" class="ui-file" />';return $(str).appendTo($("body"));
}var getUploadObj = getActiveUploadObj.after(getFlashUploadObj).after(getFormUploadObj);console.log(getUploadObj());
7.命令模式
定义:指的是 一个执行某些待定事情的指令。
应用:点菜
var client = { // 顾客(命令发出者)name: '铁蛋儿'
}
var cook = { // 厨师(命令发执行者)makeFood: function (food) {console.log('开始做:', food)},serveFood: function (client) {console.log('上菜给:', client.name)}
}function OrderCommand(receiver, food) { // 命令对象this.receiver = receiverthis.food = food
}OrderCommand.prototype.execute = function (cook) { // 提供执行方法cook.makeFood(this.food)cook.serveFood(this.receiver)
}var command = new OrderCommand(client, '宫保鸡丁')
command.execute(cook) // 开始做:宫保鸡丁; 上菜给铁蛋儿
8.备忘录模式
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
应用:新闻翻页缓存器
// Page备忘录
var Page = function() {// 缓存器var cache = {};return function(page, fn) {if (cache[page]) {// 1-如果缓存中存在指定页的数据,直接使用showPage(page, cache[page]);fn && fn();} else {// 2-如果缓存中不存在指定页的数据,则从服务端获取,并将其缓存下来$.post('server_api_url', {page: page}, function(res) {if (res.success) {showPage(page, res.data);cache[page] = res.data;fn && fn();} else {// ajax error}});}}
}();
// 事件:下一页
$('#next_page_btn').click(function() {// 获取新闻列表容器var $news = $('#news_content');// 获取当前页号var page = $news.data('page');Page(page, function() {$news.data('page', page+1);});
});
// 事件:上一页
$('#pre_page_btn').click(function() {// 获取新闻列表容器var $news = $('#news_content');// 获取当前页号var page = $news.data('page');Page(page, function() {$news.data('page', page-1);});
});
9.状态模式
定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
应用场景:超级玛丽的动作
class SuperMarry {constructor() {this._currentState = []this.states = {jump() {console.log('跳跃!')},move() {console.log('移动!')},shoot() {console.log('射击!')},squat() {console.log('蹲下!')}}}change(arr) { // 更改当前动作this._currentState = arrreturn this}go() {console.log('触发动作')this._currentState.forEach(T => this.states[T] && this.states[T]())return this}
}new SuperMarry().change(['jump', 'shoot']).go() // 触发动作 跳跃! 射击!.go() // 触发动作 跳跃! 射击!.change(['squat']).go()
10.访问者模式
定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
应用场景:使对象拥有像数组的push pop和splice方法。
var Visitor = (function() {return {splice: function(){var args = Array.prototype.splice.call(arguments, 1)return Array.prototype.splice.apply(arguments[0], args)},push: function(){var len = arguments[0].length || 0var args = this.splice(arguments, 1)arguments[0].length = len + arguments.length - 1return Array.prototype.push.apply(arguments[0], args)},pop: function(){return Array.prototype.pop.apply(arguments[0])}}})()var a = new Object()console.log(a.length)Visitor.push(a, 1, 2, 3, 4)console.log(a.length)Visitor.push(a, 4, 5, 6)console.log(a.length)Visitor.pop(a)console.log(a)console.log(a.length)Visitor.splice(a, 2)console.log(a)
11.中介者模式
定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
应用场景:MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
// 创建模型,管理多个数据class Model{model1(){return "hello";}model2(){return "world";}model3(){return "你好";}}// 创建视图,管理多种渲染方式class View{view1(data){console.log(data);}view2(data){document.write(data);}view3(data){alert(data);}}// 创建控制器,设定对应的指令class Ctrl{constructor(){// 初始化模型和视图this.m = new Model();this.v = new View();}// 在指令中,可以读取对应的数据,放在对应的视图中ctrl1(){var data = this.m.model1();this.v.view1(data);}ctrl2(){var data = this.m.model2();this.v.view3(data);}}var c = new Ctrl();c.ctrl1();c.ctrl2();