1. 设计模式原则
1. 单一职责原则(SRP)
指的是,对一个类而言(包括对象和函数),应该仅有一个引起它变化的原因,如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个,面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和第内聚的设计,当变化发生时,设计可能会遭到以外的破坏。
2. 最少知识原则(LKP)(迪米特法则)
单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性,但越来越多的对象之间可能会产生错综复杂的联系,如果修改其中一个对象,可能会影响到跟他相互引用的其他对象。最少知识原则要求我们在设计程序是,应当尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那就不要发生直接的相互联系
- 中介者模式:通过一个中介者对象,让所有的相关对象都通过中介者对象来通信,而不是互相引用,所以,当一个对象发生改变时,只需要通过中介者对象即可
- 外观模式:为一组子系统提供一个简单的访问入口,隔离客户与复杂子系统的联系,客户不用去了解子系统的细节,子系统内部的修改也不会影响到外部引用
3. 开放-封闭原则(OCP)
软件实体(类、模块、函数)等应该是可以扩展的,但是不可以修改当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码,相比修改源程序,如果增加几行代码就能解决问题,那显然更加的优雅,而且增加代码并不会影响原系统的稳定
4. 里氏转换原则
子类可以扩展父类的功能,但是不能改变父类原有的功能
1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
- 子类可以增加自己特有的方法
3.当子类方法重载父类的方法时, 方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
5. 依赖倒转原则
抽象不应该依赖细节,细节应该依赖于抽象,说白了就是面向接口编程,而不是面向实现编程
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
2. 设计模式
1. 原型模式
简介
原型模式实际上也是一种继承,可以让多个对象分享同一个原型对象的属性和方法,这种继承的实现是不需要创建的,而是将原型对象分享给那些继承的对象,原型模式是一种用来创建对象的模式,在以类为中心的语言中,要创建一个对象首先要指定这个对象的类型,然后实例化一个对象,使用原型模式创建对象时不必关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象
代码
var a = { name: 'name a', "A": function () { console.log('A'); } } function clone(superClass) { function F () {}; F.prototype = superClass; return new F(); } var b = clone(a); b.A(); // 'A'
2. 单例模式(单例就是保证一个类只有一个实例)
简介
单例就是保证一个类只有一个实例,实现的方法一般是先判断实例是否存在,如果存在直接返回,如果不存在就创建了再返回,确保了一个类只有一个实例对象,在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象
代码
var CreateDiv = function (html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
// 引入代理类
var ProxySingleton = (function () {
var instance;
return function (html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
}())
var a = new ProxySingleton('xxx');
var b = new ProxySingleton('yyy');
console.log(a === b); //true
惰性单例
// 封装获取单例函数
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
}
}
// 避免事件重复绑定
var bindEvent = getSingle(function () {
console.log('bind click'); // 只有第一次执行
document.getElementsByClassName('btn')[0].onclick = function () {
alert('box is click');
}
return true;
})
bindEvent();
bindEvent();
bindEvent();
3. 工厂模式
简介
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类,该模式使一个类的实例化延迟到了子类,而子类可以重写接口方法以便创建的时候指定自己的对象类型(理解的为当我们实例化一个对象的时候会带一个参数, 可以根据这个参数来决定实例化哪个类)
代码
// 创建一个用于继承的函数
function extend(subClass, superClass) {
// 定义一个中介类
function F() {}
// 原型指向父类的原型
F.prototype = superClass.prototype;
/*
子类的原型指向中介类的一个实例,实现继承
由于 subClass 的原型指向的是 F 的一个实例 而不是 F.prototype 也就是 父类的原型,
所以子类原型的修改不会将父类的原型也修改掉
*/
subClass.prototype = new F();
// 上一步得到的子类的原型的构造函数是 Factory ,这里需要把它变回自己本身
subClass.prototype.constructor = subClass;
}
// 创建不同交通工具 Transport
function Car() {
this.name = 'transport-car';
}
function Train() {
this.name = 'transport-train';
}
function Bike() {
this.name = 'transport-bike';
}
// 创建工厂
function Factory() {
}
Factory.prototype.create = function (type) {
var me = this;
// 注:这里改为了调用自身的 selectTransport 方法,Factory 本身没有这个方法,这个方法是子类定义的
var transport = this.selectTransport(type);
// 绑定公有的方法
transport.intro = function () {
console.log('this is ' + this.name + ' count: ' + me.count++);
}
return transport;
}
// 新增子类的创建
function TransportFactory() {
this.count = 0;
};
extend(TransportFactory, Factory); // 继承工厂
// 子类定义 selectTransport 方法,定义前一定要先继承父类,否则会被覆盖
TransportFactory.prototype.selectTransport = function (type) {
var transport;
switch (type) {
case 'car':
transport = new Car();
break;
case 'train':
transport = new Train();
break;
case 'bike':
transport = new Bike();
break;
default:
transport = new Car();
break;
}
return transport;
}
// 实例化工厂
var factory = new TransportFactory();
// 开始创建
var train = factory.create('train');
train.intro();
// 再创建
var bike = factory.create('bike');
bike.intro();
4. 观察者模式(发布订阅模式)
简介
观察者模式又叫发布订阅模式,他定义了一种一对多的关系,让多个观察者对象同时监听某一个主体对象,这个主体对象发生改变时就会通知所有观察者,使得他们能够自动更新自己提供一种抽象的策略,以便订阅者能供彼此独立的应对改变(模拟事件机制就是观察者模式订阅--发布--取消订阅)
// 定义发布平台
function Platform () {
// channel 用于存放不同频道的缓存列表
this.channel = {};
this.subId = 0;
}
//
Platform.prototype.subscribe = function (type, callback) {
// 订阅
if (!this.channel[type]) {
// 没有这个类型就创建一个类型
this.channel[type] = [];
}
// 每个类型都是一个数组,存放订阅的回调和一个标识
this.channel[type].push({
func: callback,
subId: this.subId
})
return this.subId++;
}
Platform.prototype.emit = function (type, content) {
// 发布消息
if (!this.channel[type]) {
return false;
}
this.channel[type].forEach(function (item) {
!!item.func && item.func(type, content);
})
}
Platform.prototype.unsubscribe = function (subId) {
console.log(subId);
var me = this;
for (var type in me.channel) {
if (me.channel.hasOwnProperty(type)) {
// statement
me.channel[type].forEach(function (item, i) {
Number(item.subId) === Number(subId) && me.channel[type].splice(i, 1);
})
}
}
}
// 实例化
var plat = new Platform();
var subId1 = plat.subscribe('/dev/home', function (type, content) {
console.log(`subId1 type: ${type} content: ${content}`);
})
var subId2 = plat.subscribe('/dev/device', function (type, content) {
console.log(`subId2 type: ${type} content: ${content}`);
})
var subId3 = plat.subscribe('/dev/home', function (type, content) {
console.log(`subId3 type: ${type} content: ${content}`);
})
plat.emit('/dev/home', '/dev/home is emit!');
// plat get type: /dev/home content: /dev/home is emit!
// plat get type: /dev/home content: /dev/home is emit!
plat.emit('/dev/device', '/dev/device is emit!');
// subId2 type: /dev/device content: /dev/device is emit!
// 取消订阅
plat.unsubscribe(subId1);
// 再次发布消息
plat.emit('/dev/home', '/dev/home is emit again!');
// subId3 type: /dev/home content: /dev/home is emit again!
5 中介者模式
简介
中介者模式的作用就是解除对象与对象之间的紧耦合关系,增加一个中介者对象后,所有相关的对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发生改变时,只需要通知中介者对象即可,中介者模式使得网状的多对多关系变成了相对简单的一对多关系
优缺点
- 中介者模式使得各个对象之间得以解耦,以中介中和对象之间的一对多关系取代了对象之间的网状多对多关系,各个对象只要关注自身的功能实现,对象之间的关系交给中介者实现和维护
- 最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的,难以维护的。
代码
/* ===== 创建玩家 ===== */
function Player(name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = 'alive';
}
// 游戏成功
Player.prototype.win = function () {
console.log( this.name + ' won ' );
}
// 游戏失败
Player.prototype.lose = function () {
console.log( this.name + ' lose ' );
}
// 玩家死亡
Player.prototype.die = function () {
this.state = 'dead';
// 通知中介者
playerDirector.reciveMessage( 'playerDead', this );
}
/* ===== 创建玩家的工厂 ===== */
var playerFactory = function (name, teamColor) {
var newPlayer = new Player(name, teamColor);
playerDirector.reciveMessage( 'addPlayer', newPlayer );
return newPlayer;
}
/* ===== 创建游戏平台(中介) ===== */
var playerDirector = (function () {
// 玩家集合
var players = {};
// 操作集合
var operations = {};
// 增加
operations.addPlayer = function (player) {
var teamColor = player.teamColor;
players[teamColor] = players[teamColor] || [];
players[teamColor].push(player);
}
operations.playerDead = function (player) {
var teamColor = player.teamColor;
// 找到玩家所在队伍
var teamPlayers = players[teamColor];
var all_dead = true;
for (var i = 0; i < teamPlayers.length; i++) {
if (teamPlayers[i].state !== 'dead') {
all_dead = false;
break;
}
}
// 全部死亡发布结果
if (all_dead === true) {
// 本队全部失败
for (var k = 0; k < teamPlayers.length; k++) {
teamPlayers[k].lose();
}
// 其他队伍全部成功
for ( var color in players ){
if ( color !== teamColor ){
var otherTeamPlayers = players[ color ];
for ( var j = 0; j < otherTeamPlayers.length; j++){
otherTeamPlayers[j].win();
}
}
}
}
}
var reciveMessage = function () {
var message = Array.prototype.shift.call(arguments);
operations[ message ].apply( this, arguments );
}
return {reciveMessage};
}())
// 测试
// 红队:
var player1 = playerFactory( 'AA', 'red' ),
player2 = playerFactory( 'BB', 'red' ),
player3 = playerFactory( 'CC', 'red' ),
player4 = playerFactory( 'DD', 'red' );
// 蓝队:
var player5 = playerFactory( 'aa', 'blue' ),
player6 = playerFactory( 'bb', 'blue' ),
player7 = playerFactory( 'cc', 'blue' ),
player8 = playerFactory( 'dd', 'blue' );
player1.die();
player2.die();
player3.die();
player4.die();
/*
AA lose
BB lose
CC lose
DD lose
aa won
bb won
cc won
dd won
*/
6.状态模式
简介
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变,通常我们谈到封装,一般都会优先封装对象的行为,而不是对象的状态,但在状态模式中刚好相反,状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,执行上下文中,请求直接委托给状态对象,该状态对象会负责渲染它自身的行为
优缺点
1.状态模式定义了状态与行为之间的关系,并将他们封装在一个类中,通过增加新的状态类,很容易增加新的状态和转换
- 避免执行上下文 Context 无线膨胀,切换状态的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支
- 用对象代替字符串记录当前状态,使得状态的切换更加的一目了然
- 缺点是会在系统中定义许多的状态类和对象,另外由于逻辑分散在状态类中,造成了逻辑分散的问题,无法在一个地方看出整个状态机的逻辑
代码
// 定义状态类
var FSM = {
off: {
// 状态对应的行为
buttonWasPressed: function () {
console.log('关灯');
this.button.innerHTML = '下次开灯';
// 定义好要切换到的下一个状态
this.currState = FSM.on;
}
},
on: {
buttonWasPressed: function () {
console.log('开灯');
this.button.innerHTML = '下次关灯';
this.currState = FSM.off;
}
}
}
var Light = function () {
// 设置初始状态
this.currState = FSM.off;
this.button = null;
}
Light.prototype.init = function () {
var button = document.createElement('button');
var self = this;
button.innerHTML = '已关灯'
this.button = document.body.appendChild(button);
this.button.onclick = function () {
self.currState.buttonWasPressed.call(self);
}
}
var light = new Light();
light.init();
状态模式和策略模式
共同点:都封装了一系列的算法或者行为,都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行
不同点:策略模式中,的各个策略类是平等又平行的,他们之间没有任何联系,算法切换是用户主动完成的;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部,客户不需要了解这些细节
7. 装饰者模式
简介
装饰者模式可以动态的给某个对象添加一些额外的职责,而不会影响从这个类中派生出的其他对象
优势
- 传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活,还会带来许多问题:一方面导致超类和子类之间存在强耦合性,当超类改变时,子类页随之改变;另一方面,继承方式中,超类的内部细节对子类式可见的,破坏了封装性
- 装饰者模式能够在不改变对象自身的基础上,在程序运行的期间给对象动态添加职责,跟继承相比,装饰者模式是一种轻便灵活的做法,是一种 ‘即用即付’ 的方式
代码
// 给 Function 构造函数添加 before 和 after 两个方法
Function.prototype.before = function (beforeFn) {
// 保存原函数的引用
var _self = this;
// 返回包含了原函数和新函数的‘代理函数’
return function () {
beforeFn.apply(this, arguments);
// 执行原函数并返回原函数的执行结果
return _self.apply(this, arguments);
}
}
// 与 before 的区别是新旧函数的执行顺序
Function.prototype.after = function (afterFn) {
var _self = this;
return function () {
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
}
// 使用
var ajax = function (type, url, param) {
console.log('发送 ajax 请求 ', param);
}
var getToken = function () {
return 'Token';
}
// 装饰
ajax = ajax.before(function (type, url, param) {
param.Token = getToken();
});
ajax('get', 'http://xxx.com', {name: 'ajax'}); // 发送 ajax 请求 {name: "ajax", Token: "Token"}
对比
- 装饰者模式与代理模式
- 代理模式的目的是,当直接访问本体不方便或者不符合需要时,这个本体提供一个替代者,本体提供了关键功能,而代理提供或拒绝访问,或者访问前做额外的事
- 装饰者模式作用就是为对象动态加入行为
- 代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链
- 装饰者模式与外观模式
- 外观模式,是对一系列操作的封装,并对外提供接口
- 装饰者模式,是对某一个对象进行内容附加,提供额外的行为,扩展功能
8.享元模式
简介
享元模式是一种用于性能优化的模式,它的核心是运用共享技术来有效支持大量细粒度的对象
内部状态和外部状态
享元模式要求将对象的属性划分为内部状态与外部状态(状态指的是属性),享元模式的目标是减少共享对象的数量
状态划分
1.内部状态存储于对象内部
2.内部状态被一些对象共享
3.内部状态独立于具体的场景,通常不会改变
4.外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
我们可以把所有内部状态指定到一个共享对象上,而外部状态剥离出去,存储在外部,也就是说剥离了外部状态的对象成为共享对象,外部状态在必要的时候被传入共享对象组装成一个完整的对象,虽然组装外部状态的过程需要花费一定的时间,但却可以减少系统中对象的数量, 因此享元模式是一种用时间换空间的优化模式
使用场景
一个程序中使用了大量的相似对象
由于使用了大量的对象,造成了很大的内存开销
对象的大多数状态都可以变为外部状态
剥离出对象的外部状态之后,可以使用相对较少的共享对象取代大量对象
代码
// 创建模特类
var Model = function (sex) {
// 内部状态
this.sex = sex;
}
// 快照
Model.prototype.takePhoto = function () {
console.log('sex = ' + this.sex + ' underwear = ' + this.underwear);
}
// 创建男模特和女模特,一共只创建两个
var maleModel = new Model('male');
var femaleModel = new Model('female');
// 穿衣服拍照,装配 underwear 外部状态
for (var i = 0; i < 50; i++) {
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
}
for (var i = 0; i < 50; i++) {
femaleModel.underwear = 'underwear' + i;
femaleModel.takePhoto();
}
对象池
对象池维护一个装载空闲对象的池子,如果需要对象时,不直接创建,而是转从对象池里获取,如果对象池里没有空闲对象,则创建一个新对象,当获取的对象完成了他的职责后,再进入池子等待被下次获取,这也是一种共享技术
// 定义气泡创建工厂
var toolTipFactory = (function () {
// 创建对象池
var toolTipPool = [];
return {
// 创建
create: function () {
if (toolTipPool.length === 0) {
// 对象池为空,创建
var div = document.createElement('div');
document.body.appendChild(div);
return div;
} else {
// 对象池不为空,直接返回对象池中对象
return toolTipPool.shift();
}
},
// 移入对象池
recover: function (toolTipDom) {
return toolTipPool.push(toolTipDom);
},
getToolTipPool: function () {
return toolTipPool;
}
}
}())
// 首次创建
var ary = [];
var strs = [ 'A', 'B' ];
for ( var i = 0; i < strs.length; i++){
var toolTip = toolTipFactory.create();
toolTip.innerHTML = strs[i];
ary.push( toolTip );
}
// 会受到对象池
for (var i = 0; i < ary.length; i++) {
toolTipFactory.recover(ary[i]);
}
console.log(toolTipFactory.getToolTipPool()); // [div, div]
// 再次创建
var strs2 = [ 'A', 'B', 'C', 'D', 'E', 'F' ];
for ( var i = 0; i < strs2.length; i++){
var toolTip = toolTipFactory.create();
toolTip.innerHTML = strs2[i];
ary.push( toolTip );
}
console.log(toolTipFactory.getToolTipPool()); // []
9. 策略模式
简介
策略模式定义了一系列算法,从概念上来说,所有的这些算法都是做相同的事情,只是实现不同,他可以以相同的方式调用所有的方法,减少了各种算法与使用算法之间的耦合,单独定义算法类,方便单元测试, 不仅可以封装算法,也可以封装任何类型的规则
一个机遇策略模式的程序至少由两部分组成
第一部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程
第二部分是环境类 Context,Context接受客户的请求,随后把请求委托给某一个策略类
代码
// 定义策略组,(将定义的一组算法封装起来)
var strategies = {
'S': function (amount) {
return amount * 1;
},
'A': function (amount) {
return amount * 2;
},
'B': function (amount) {
return amount * 3;
}
};
function calculate(level, amount) {
return strategies[level](amount);
}
console.log(calculate('B', 222)); // 666
function Valid(dom) {
this.dom = dom;
}
Valid.prototype.utils = {
isEmpty: function (flag, val) {
return !val === flag;
},
isTel: function (flag, val) {
var reg = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/;
return reg.test(val) === flag;
},
isMail: function (flag, val) {
var reg = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
return reg.test(val) === flag;
}
}
Valid.prototype.proof = function (config) {
// 验证
var val = this.dom.value;
var pass = true;
for (var conf in config) {
if (config.hasOwnProperty(conf)) {
var testFn = this.utils[conf];
var retVal = testFn(config[conf], val);
if (!retVal) {
pass = false;
}
}
}
return pass;
}
var dom = document.getElementById('input');
var input = new Valid(dom);
dom.onblur = function () {
var pass = input.proof({
isEmpty: false,
isTel: true,
isMail: false
})
console.log(pass);
}