前端常用的设计模式
前言
设计模式定义:在面向对象软件设计过程中 针对特定问题的简洁而优雅的解决方案。在不同的编程语言中,对设计模式的实现其实是可能会有区别的。
- 单例模式
- 观察者模式
- 工厂模式
- 命令模式
- 职责链模式
1,单例模式
定义:是保证一个类只有一个实例,并且提供一个访问它的全局访问点。
需求:一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象、登录浮窗等。
实现:用一个变量标识当前是否已经为某个类创建过对象,如果是,则在下一次获取这个类的实例时,直接返回之前创建的对象。
优点:
- 可以用来划分命名空间,减少全局变量的数量
- 可以被实例化,且实例化一次,再次实例化生成的也是第一个实例
基础栗子:
// 单例模式 var Singleton = function(name){ this.name = name; this.instance = null; }; Singleton.prototype.getName = function(){ return this.name; }; // 获取实例对象 Singleton.getInstance = function(name) { if(!this.instance) { this.instance = new Singleton(name); } return this.instance; }; // 测试单例模式的实例 var a = Singleton.getInstance("aa"); var b = Singleton.getInstance("bb"); console.log(a===b) // true
实践栗子:
(function () { //管理单例的逻辑代码,如果没有数据则创建,有数据则返回 var getSingle = function(fn){ //参数为创建对象的方法 var result; return function(){ //判断是Null或赋值 return result || (result = fn.apply(this,arguments)); }; }; //创建登录窗口方法 var createLoginLayer = function(){ var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; }; //单例方法 var createSingleLoginLayer = getSingle(createLoginLayer); //使用惰性单例,进行创建 document.getElementById('loginBtn').onclick = function(){ var loginLayer = createSingleLoginLayer(); loginLayer.style.display = 'block'; }; })()
2,观察者模式
定义:对象间的一种一对多的依赖关系。
需求:当一个对象的状态发生变化时,所有依赖于他的对象都将得到通知。
优点:时间上的解耦,对象之间的解耦。
实现:
- 首先,指定好谁充当发布者;
- 然后,给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
- 最后,发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。
基础栗子:
var salesOffices = {}; // 定义售楼处 salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数 salesOffices.listen = function( fn ){ // 增加订阅者 this.clientList.push( fn ); // 订阅的消息添加进缓存列表 }; salesOffices.trigger = function(){ // 发布消息 for( var i = 0, fn; fn = this.clientList[ i++ ]; ){ fn.apply( this, arguments ); // arguments 是发布消息时带上的参数 } }; //调用 salesOffices.listen( function( price, squareMeter ){//订阅消息 console.log( '价格= ' + price ); console.log( 'squareMeter= ' + squareMeter ); }); salesOffices.trigger( 2000000, 88 ); // 输出:200 万,88 平方米
实践栗子:登录页面登录后,会需要刷新各个模块的信息(头像、nav)这类。
var ObserverEvent = (function () { var clientList = [], listen, trigger, remove; listen = function (key, fn) { if (!clientList[key]) { clientList[key] = []; } clientList[key].push(fn); }; trigger = function () { var key = Array.prototype.shift.call(arguments), fns = clientList[key]; if (!fns || fns.length === 0) { return false; } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } }; remove = function (key, fn) { var fns = clientList[key]; if (!fns) { return false; } if (!fn) { fns && (fns.length = 0); } else { for (var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l]; if (_fn === fn) { fns.splice(l, 1); } } } }; return { listen:listen, trigger:trigger, remove:remove } })(); ObserverEvent.listen('squareMeter88', fn1 = function (price) { console.log('价格=' + price); }); ObserverEvent.listen('squareMeter100', function (price) { console.log('价格=' + price); }); ObserverEvent.trigger('squareMeter88', 200000); //刷新模块信息 var header = (function () { ObserverEvent.listen('loginSucc', function (data) { header.setAvatar(data.avatar); }); return { setAvatar: function (data) { console.log(data + "设置header成功"); } } })(); var nav = (function () { ObserverEvent.listen('loginSucc', function (data) { nav.setAvatar(data.avatar) }); return { setAvatar: function (data) { console.log(data + '设置nav成功'); } } })(); var data = {}; data.avatar = "参数"; ObserverEvent.trigger('loginSucc', data);
3,工厂模式:
定义:将其成员对象的实例化推迟到子类来实现的类。
需求:创建对象的流程赋值的时候,比如依赖于很多设置文件等 ;处理大量具有相同属性的小对象;注:不能滥用
优点:不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中。
分类:简单工厂,工厂方法和抽象工厂。
实现:
3.1 简单工厂模式 (创建单一对象,需要的类比较少)
let UserFactory = function (role) { function SuperAdmin() { this.name = "超级管理员", this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理'] } function Admin() { this.name = "管理员", this.viewPage = ['首页', '通讯录', '发现页', '应用数据'] } function NormalUser() { this.name = '普通用户', this.viewPage = ['首页', '通讯录', '发现页'] } switch (role) { case 'superAdmin': return new SuperAdmin(); break; case 'admin': return new Admin(); break; case 'user': return new NormalUser(); break; default: throw new Error('参数错误, 可选参数:superAdmin、admin、user'); } }
3.2 工厂方法模式 (创建多类对象,需要的类比较多)
为方便后续新增类方便,只需改一处代码,封装了工厂方法而已。并且把类都放在工厂类原型中实现。
//安全模式创建的工厂方法函数 let UserFactory = function(role) { if(this instanceof UserFactory) { var s = new this[role](); return s; } else { return new UserFactory(role); } } //工厂方法函数的原型中设置所有对象的构造函数 UserFactory.prototype = { SuperAdmin: function() { this.name = "超级管理员", this.viewPage = ['首页', '通讯录', '发现页', '应用数据', '权限管理'] }, Admin: function() { this.name = "管理员", this.viewPage = ['首页', '通讯录', '发现页', '应用数据'] }, NormalUser: function() { this.name = '普通用户', this.viewPage = ['首页', '通讯录', '发现页'] } } //调用 let superAdmin = UserFactory('SuperAdmin'); let admin = UserFactory('Admin') let normalUser = UserFactory('NormalUser')
3.3 抽象工厂模式 (创建父类,子类继承父类,具体实现在子类)
抽象工厂其实是实现子类继承父类的方法,只是一个方法。
抽象工厂模式一般用在多人协作的超大型项目中,并且严格的要求项目以面向对象的思想进行完成。
// 抽象工厂方法 var VehicleFatory = function(subType, superType) { // 判断抽象工厂中是否有该抽象类 if(typeof VehicleFactory[superType] === 'function') { // 缓存类 function F() {}; // 继承父类属性和方法 F.prototype = new VehicleFactory[superType] (); // 将子类constructor 指向子类 subType.constructor = subType; // 子类原型继承'父类' subType.prototype = new F(); } else { // 不存在该抽象类抛出错误 throw new Error('未创建该抽象类'); } }; // 小汽车抽象类 VehicleFactory.Car = function() { this.type = 'car'; }; VehicleFactory.Car.prototype = { getPrice: function() { return new Error('抽象方法不能调用'); }, getSpeed: function() { return new Error('抽象方法不能调用'); } }; // 公交车抽象类 VehicleFactory.Bus = function() { this.type = 'bus'; }; VehicleFactory.Bus.prototype = { getPrice: function() { return new Error('抽象方法不能调用'); }, getSpeed: function() { return new Error('抽象方法不能调用'); } }; // 货车抽象类 VehicleFactory.Truck = function() { this.type = 'truck'; }; VehicleFactory.Truck.prototype = { getPrice: function() { return new Error('抽象方法不能调用'); }, getSpeed: function() { return new Error('抽象方法不能调用'); } }; // 创建产品子类继承相应的产品簇抽象类 // 宝马汽车子类 var BMW = function(price, speed) { this.price = price; this.speed = speed; } //抽象工厂实现对Car抽象类的继承 VehicleFactory(BMW, 'Car'); BMW.prototype.getPrice = function() { return this.price }; BMW.prototype.getSpeed = function() { return this.speed }; // 公交车... // 货车...
4,命令模式:
定义:用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行。
需求:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
实现:将函数的调用、请求和操作封装成一个单一的对象。
1 var setCommand = function(button,func) { 2 button.onclick = function(){ 3 func(); 4 } 5 }; 6 var MenuBar = { 7 refersh: function(){ 8 alert("刷新菜单界面"); 9 } 10 }; 11 var SubMenu = { 12 add: function(){ 13 alert("增加菜单"); 14 } 15 }; 16 // 刷新菜单 17 var RefreshMenuBarCommand = function(receiver) { 18 return function(){ 19 receiver.refersh(); 20 }; 21 }; 22 // 增加菜单 23 var AddSubMenuCommand = function(receiver) { 24 return function(){ 25 receiver.add(); 26 }; 27 }; 28 var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar); 29 // 增加菜单 30 var addSubMenuCommand = AddSubMenuCommand(SubMenu); 31 setCommand(b1,refershMenuBarCommand); 32 33 setCommand(b2,addSubMenuCommand);
5,职责链模式:
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。(大函数分割成一个个小函数,清晰,各司其职)
需求:代码不清晰,可读性差,拆分函数。
实现:
//----------------------改造前---------------
var order = function( orderType, pay, stock ){ if ( orderType === 1 ){ // 500 元定金购买模式 if ( pay === true ){ // 已支付定金 console.log( '500 元定金预购, 得到 100 优惠券' ); }else{ // 未支付定金,降级到普通购买模式 if ( stock > 0 ){ // 用于普通购买的手机还有库存 console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } } else if ( orderType === 2 ){ // 200 元定金购买模式 if ( pay === true ){ console.log( '200 元定金预购, 得到 50 优惠券' ); }else{ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } } else if ( orderType === 3 ){ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } } }; order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
//--------------------- 改造后----------------------------
// 500 元订单 var order500 = function( orderType, pay, stock ){ if ( orderType === 1 && pay === true ){ console.log( '500 元定金预购, 得到 100 优惠券' ); }else{ order200( orderType, pay, stock ); // 将请求传递给 200 元订单 } }; // 200 元订单 var order200 = function( orderType, pay, stock ){ if ( orderType === 2 && pay === true ){ console.log( '200 元定金预购, 得到 50 优惠券' ); }else{ orderNormal( orderType, pay, stock ); // 将请求传递给普通订单 } }; // 普通购买订单 var orderNormal = function( orderType, pay, stock ){ if ( stock > 0 ){ console.log( '普通购买, 无优惠券' ); }else{ console.log( '手机库存不足' ); } }; // 测试结果: order500( 1 , true, 500); // 输出:500 元定金预购, 得到 100 优惠券 order500( 1, false, 500 ); // 输出:普通购买, 无优惠券 order500( 2, true, 500 ); // 输出:200 元定金预购, 得到 500 优惠券 order500( 3, false, 500 ); // 输出:普通购买, 无优惠券 order500( 3, false, 0 ); // 输出:手机库存不足