今天主要介绍一下咱们日常会常常用到的设计模式,设计模式总的来讲有23种,而设计模式在前端中又该怎么运用呢,接下来主要对比较前端中常见的设计模式作一个介绍前端
1、什么是设计模式vue
通常来讲,设计模式表明了最佳的实践,一般被有经验的面向对象的软件开发人员所采用,在咱们平时的软件开发中,常常须要用到各类设计模式,设计模式是一套被反复使用的、多数人知晓的、通过分类编目的、代码设计经验的总结,使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。react
设计模式能够说是软件工程的基石,合理的使用设计模式,可使咱们的代码真正的工程化,在项目中使用设计模式能够完美的解决不少问题,在设计模式中,大概来讲总共有23种,而具体要用哪种还须要根据状况而定,就像平时在前端开发中,我比较熟悉的就是工厂模式,原型模式和MVC这些模式啦,接下来主要对其中的一些设计模式进行一个比较详细的介绍。算法
2、设计模式的分类编程
首先,仍是须要先说一下设计模式的分类,刚才说到设计模式总的来讲有23种,而这23种,又能够分为如下四大类设计模式
一、建立型模式安全
建立型模式提供了一种在建立对象的同时隐藏建立逻辑的方式,而不是使用 new 运算符直接实例化对象,这使得程序在判断针对某个给定实例须要建立哪些对象时更加灵活,主要包括如下几种:闭包
工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式 架构
二、结构型模式框架
结构型模式关注类和对象的组合继承的概念被用来组合接口和定义组合对象得到新功能的方式,主要包括如下几种:
适配器模式、桥接模式、过滤器模式、组合模式、装饰器模式、外观模式、享元模式、代理模式
三、行为型模式
行为型模式关注对象之间的通讯,主要包括如下几种:
责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、空对象模式、策略模式、模板模式、访问者模式
四、J2EE模式
J2EE模式关注表示层,这些模式是由 Sun Java Center 鉴定的,主要包括如下几种:
MVC 模式、业务表明模式、组合实体模式、数据访问对象模式、前端控制器模式、拦截过滤器模式、服务定位器模式、传输对象模式
3、设计模式六大原则
上面介绍了几种不一样设计的模式,而全部的设计模式都须要遵循下面的六大原则
一、开闭原则
开闭原则的意思是:对扩展开放,对修改关闭,在程序须要进行拓展的时候,不能去修改原有的代码
二、里氏代换原则
里氏代换原则中说,任何基类能够出现的地方,子类必定能够出现,里氏代换原则是对开闭原则的补充,实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范
三、依赖倒转原则
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体
四、接口隔离原则
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好,它还有另一个意思是:下降类之间的耦合度,此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调下降依赖,下降耦合
五、最少知道原则
最少知道原则是指:一个实体应当尽可能少地与其余实体之间发生相互做用,使得系统功能模块相对独立
六、合成复用原则
合成复用原则是指:尽可能使用合成/聚合的方式,而不是使用继承
4、常见的设计模式
设计模式有不少种,接下来,我将介绍其中的几种,而且介绍这些设计模式怎么运用在前端中
一、工厂模式
工厂模式是用来建立对象的一种最经常使用的设计模式,咱们不暴露建立对象的具体逻辑,而是将将逻辑封装在一个函数中,那么这个函数就能够被视为一个工厂,工厂模式根据抽象程度的不一样能够分为:简单工厂,工厂方法和抽象工厂,接下来,将对简单工厂和工厂方法在JavaScript中的运用举个简单的例子
(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'); } } //调用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user')
在上面的例子中,UserFactory
就是一个简单工厂,在该函数中有3个构造函数分别对应不一样的权限的用户,当咱们调用工厂函数时,只须要传递superAdmin
, admin
, user
这三个可选参数中的一个获取对应的实例对象
优势:简单工厂的优势在于,你只须要一个正确的参数,就能够获取到你所须要的对象,而无需知道其建立的具体细节
缺点:在函数内包含了全部对象的建立逻辑(构造函数)和判断逻辑的代码,每增长新的构造函数还须要修改判断逻辑代码,咱们的对象不是上面的3个而是30个或更多时,这个函数会成为一个庞大的超级函数,便得难以维护,简单工厂只能做用于建立的对象数量较少,对象的建立逻辑不复杂时使用
(2)工厂方法
工厂方法模式的本意是将实际建立对象的工做推迟到子类中,这样核心类就变成了抽象类,可是在JavaScript中很难像传统面向对象那样去实现建立抽象类,因此在JavaScript中咱们只须要参考它的核心思想便可,咱们能够将工厂方法看做是一个实例化对象的工厂类
好比说上面的例子,咱们用工厂方法能够这样写,工厂方法咱们只把它看做是一个实例化对象的工厂,它只作实例化对象这一件事情,咱们采用安全模式建立对象
//安全模式建立的工厂方法函数 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')
在简单工厂中,若是咱们新增长一个用户类型,须要修改两个地方的代码,一个是增长新的用户构造函数,一个是在逻辑判断中增长对新的用户的判断,而在抽象工厂方法中,咱们只须要在UserFactory.prototype中添加就能够啦
二、代理模式
代理模式主要是为其余对象提供一种代理以控制对这个对象的访问,主要解决在直接访问对象时带来的问题,好比说:要访问的对象在远程的机器上,在面向对象系统中,有些对象因为某些缘由(好比对象建立开销很大,或者某些操做须要安全控制,或者须要进程外的访问),直接访问会给使用者或者系统结构带来不少麻烦,咱们能够在访问此对象时加上一个对此对象的访问层
代理模式最基本的形式是对访问进行控制,代理对象和另外一个对象(本体)实现的是一样的接口,实际上工做仍是本体在作,它才是负责执行所分派的任务的那个对象或类,代理对象所作的不外乎节制对本体的访问,代理对象并不会在另外一对象的基础上添加方法或修改其方法,也不会简化那个对象的接口,它实现的接口与本体彻底相同,全部对它进行的方法调用都会被传递给本体
(function(){ // 示例代码 // 目标对象,是真正被代理的对象 function Subject(){} Subject.prototype.request = function(){}; /** * 代理对象 * @param {Object} realSubject [持有被代理的具体的目标对象] */ function Proxy(realSubject){ this.realSubject = readSubject; } Proxy.prototype.request = function(){ this.realSubject.request(); }; }());
在上面的代码中,Proxy能够控制对真正被代理对象的一个访问,在代理模式中,比较常见的就是虚拟代理,虚拟代理用于控制对那种建立开销很大的本体的访问,它会把本体的实例化推迟到有方法被调用的时候,好比说,如今咱们假设PublicLibrary的实例化很慢,不能在网页加载的时候当即完成,咱们能够为其建立一个虚拟代理,让它把PublicLibrary的实例化推迟到必要的时候,好比说咱们在前端中常常用到的图片懒加载,就能够用虚拟代理
三、观察者模式
若是你们学过一些像vue,react这些框架,相信你们对观察者模式必定很熟悉,如今不少mvvm框架都用到了观察者模式这个思想,观察者模式又叫作发布—订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知和更新,观察者模式提供了一个订阅模型,其中对象订阅事件并在发生时获得通知,这种模式是事件驱动的编程基石,它有利益于良好的面向对象的设计
下面举个例子,好比咱们给页面中的一个dom节点绑定一个事件,其实就能够看作是一种观察者模式
document.body.addEventListener("click", function() { alert("Hello World") },false ) document.body.click() //模拟用户点击
在上面的例子中,须要监听用户点击 document.body 的动做,可是咱们是没办法预知用户将在何时点击的,所以咱们订阅了 document.body 的 click 事件,当 body 节点被点击时,body 节点便会向订阅者发布 "Hello World" 消息
四、单例模式
单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点,保证一个类只有一个实例,实现的方法通常是先判断实例存在与否,若是存在直接返回,若是不存在就建立了再返回,这就确保了一个类只有一个实例对象
下面举个例子,在js中,咱们可使用闭包来建立实现这种模式
var single = (function(){ var unique; function getInstance(){ // 若是该实例存在,则直接返回,不然就对其实例化 if( unique === undefined ){ unique = new Construct(); } return unique; } function Construct(){ // ... 生成单例的构造函数的代码 } return { getInstance : getInstance } })();
在上面的代码中,咱们可使用single.getInstance来获取到单例,而且每次调用均获取到同一个单例,在咱们平时的开发中,咱们也常常会用到这种模式,好比当咱们单击登陆按钮的时候,页面中会出现一个登陆框,而这个浮窗是惟一的,不管单击多少次登陆按钮,这个浮窗只会被建立一次,所以这个登陆浮窗就适合用单例模式
五、策略模式
策略模式指的是定义一些列的算法,把他们一个个封装起来,目的就是将算法的使用与算法的实现分离开来,避免多重判断条件,更具备扩展性
下面也是举个例子,如今超市有活动,vip为5折,老客户3折,普通顾客没折,计算最后须要支付的金额,若是不使用策略模式,咱们的代码可能和下面同样
function Price(personType, price) { //vip 5 折 if (personType == 'vip') { return price * 0.5; } else if (personType == 'old'){ //老客户 3 折 return price * 0.3; } else { return price; //其余都全价 } }
在上面的代码中,咱们须要不少个判断,若是有不少优惠,咱们又须要添加不少判断,这里已经违背了刚才说的设计模式的六大原则中的开闭原则了,若是使用策略模式,咱们的代码能够这样写
// 对于vip客户 function vipPrice() { this.discount = 0.5; } vipPrice.prototype.getPrice = function(price) { return price * this.discount; } // 对于老客户 function oldPrice() { this.discount = 0.3; } oldPrice.prototype.getPrice = function(price) { return price * this.discount; } // 对于普通客户 function Price() { this.discount = 1; } Price.prototype.getPrice = function(price) { return price ; } // 上下文,对于客户端的使用 function Context() { this.name = ''; this.strategy = null; this.price = 0; } Context.prototype.set = function(name, strategy, price) { this.name = name; this.strategy = strategy; this.price = price; } Context.prototype.getResult = function() { console.log(this.name + ' 的结帐价为: ' + this.strategy.getPrice(this.price)); } var context = new Context(); var vip = new vipPrice(); context.set ('vip客户', vip, 200); context.getResult(); // vip客户 的结帐价为: 100 var old = new oldPrice(); context.set ('老客户', old, 200); context.getResult(); // 老客户 的结帐价为: 60 var Price = new Price(); context.set ('普通客户', Price, 200); context.getResult(); // 普通客户 的结帐价为: 200
在上面的代码中,经过策略模式,使得客户的折扣与算法解藕,又使得修改跟扩展能独立的进行,不影到客户端或其余算法的使用
当咱们的代码中有不少个判断分支,每个条件分支都会引发该“类”的特定行为以不一样的方式做出改变,这个时候就可使用策略模式,能够改进咱们代码的质量,也更好的能够进行单元测试
今天就写到这里了,其实还有不少设计模式,在这里尚未进行总结,你们有空的话也能够本身去了解