此篇博客我主要针对以下几种模式进行介绍: 单例模式、工厂模式、策略模式、代理模式、发布订阅模式以及观察者模式六种模式。
什么是设计模式呢?
设计模式主要针对软件设计中普遍存在的各种问题所提出的解决方案,类似于由前辈们总结的设计经验,让我们少走弯路,能够使不稳定依赖于相对稳定、具体依赖于相对抽象,减少紧耦合,增强软件设计面对并适应变化的能力。
1.单例模式
是一种创建型模式,提供了一种创建对象的最佳方式,涉及道了一个单一的类,该类只在全局作用域下创建一次实例对象,让所用需要调用的地方都共享这一单例对象。
举个例子吧,比如说你有一台汽车,一天早上一个朋友借用了你的汽车,你说:‘欧克,没问题,拿去用吧’,之后呢,下午又有一个朋友问你借汽车,你说:‘抱歉,汽车已经被别人借去了’。
使用场景:模态框的使用,第一次使用的时候会创建该模态框,之后呢,再次使用就直接用原来的创建好的模态框即可。
const getWindow =function (fn){
// 利用闭包,保存已经创建好的实例
let window
return function(){
// 看看有没有实例,如果有的话,则直接返回,没有的话,就创建一个,并返回
return window || (window =fn.apply(this,arguments))
}
}
// 创建模态框
const createWindow =function(){
var div =document.createElement('div')
div.innerHTML ='我是浮窗'
div.style.display ='none'
document.body.appendChild(div)
return div
}
const createSingleWindow = getWindow(createWindow)
// 给指定元素添加事件
document.getElementById('xx').onclick=function(){
// 得到当前窗口
var windowElement = createSingleWindow()
// 设置其为显示的
windowElement.style.display ='block'
}
2.工厂模式
用来创建对象的一种最常用的设计模式,不会暴漏创建对象的具体逻辑,而是将逻辑封装在一个函数中,这个函数就是一个工厂
工厂模式又包含了三种模式:简单工厂模式 、 工厂方法模式、抽象工厂模式。
(1)简单工厂模式(类似于:一个工厂生产多件商品)
例子:根据传入的身份,来显示不同的职业工作(传入的身份不同,那么就显示不同的工作)
function Factory(career){
function Work(career,task){
this.career =carerr
this.tast =task
}
let task
switch(career){
case 'code':
task=['写代码','修bug']
return Work(career,task)
break
case 'hr':
task=['面试新人','谈薪资']
return Work(career,task)
break
}
}
let hr =new Factory('hr')
console.log(hr)
let code =new Factory('code')
console.log(code)
优点:简单工厂顾名思义,实现比较简单,只需要传入特定参数即可
缺点: 违背了开闭原则(也就是一旦要增加新的产品,就要修改工厂类)
(2)工厂方法模式(类似于:多个工厂,但每个工厂只生产一个特定的商品)
例子:同样是根据身份,来生成不同的工作任务
function Factory(career){
// 这个是为了解决 对类创建的错误使用
if(this instanceof Factory){
var a =new this[career]()
return a
}
else{
return new Factory(career)
}
}
// 在原型上,设置构造函数
Factoyr.prototype={
'code':function(){
this.task=['写代码']
this.career='程序员'
},
'hr':function(){
this.task=['面试新人']
this.career='HR'
}
}
let hr =new Factory('hr')
console.log(hr)
优点:符合开闭原则
缺点:每次新增产品,产品类都需要创建对应的工厂类
(3)抽象工厂模式(类似于:一个工厂可以生产多类产品,A工厂可以生产A品牌的自行车、摩托车,B工厂可以生产B品牌的自行车,摩托车)
/* 工厂 抽象类 */
class AbstractFactory {
constructor() {
if (new.target === AbstractFactory)
throw new Error('抽象类不能直接实例化!')
}
/* 抽象方法 */
createProduct1() { throw new Error('抽象方法不能调用!') }
}
/* 具体饭店类 */
class Factory extends AbstractFactory {
constructor() { super() }
createProduct1(type) {
switch (type) {
case 'Product1':
return new Product1()
case 'Product2':
return new Product2()
default:
throw new Error('当前没有这个产品 -。-')
}
}
}
/* 抽象产品类 */
class AbstractProduct {
constructor() {
if (new.target === AbstractProduct)
throw new Error('抽象类不能直接实例化!')
this.kind = '抽象产品类1'
}
/* 抽象方法 */
operate() { throw new Error('抽象方法不能调用!') }
}
/* 具体产品类1 */
class Product1 extends AbstractProduct {
constructor() {
super()
this.type = 'Product1'
}
operate() { console.log(this.kind + ' - ' + this.type) }
}
/* 具体产品类2 */
class Product2 extends AbstractProduct {
constructor() {
super()
this.type = 'Product2'
}
operate() { console.log(this.kind + ' - ' + this.type) }
}
const factory = new Factory()
const prod1 = factory.createProduct1('Product1')
prod1.operate() // 输出: 抽象产品类1 - Product1
const prod2 = factory.createProduct1('Product3')
优点:隔离了具体类的生成(客户端并不需要知道其什么时候被创建的)
缺点:扩展是非常费力的,要修改很多的类
3.策略模式
指的是定义一系列的算法,把它们一个个封装起来,目的就是将算法的使用与算法的实现分离开始
一个基于策略模式的程序至少由两部分组成:
(1)策略类(策略类封装了具体的算法,并负责具体的计算过程)
(2)环境类Context, 用来接收客户的请求,随后把请求委托给某一个策略类
例子:根据等级,来计算对应的年终奖
// obj 即为策略类 将算法封装,并且计算
var obj={
'A':function(salary){
return salary*4
},
'B':function(salary){
return salary*3
}
}
// 即为环境类,接收用户的请求,将其委派给策略类
var getPrice=function(level,salary){
return obj[level](salary)
}
console.log(getPrice('B',200)) // 600
优点: (1)符合开闭原则
(2)避免使用多重添加转换语句
(3) 也可以提高算法的安全性和保密性
缺点: (1)客户端必须知道所有的策略,并且自行决定使用哪一个策略
(2)代码中会产生非常多策略类,增加维护难度
4.代理模式
是一个为对象提供一个代用品或占位符,以便控制对它的访问,代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要时,提供一个替身对象来控制这个对象的访问。
举个例子,你租房子或者买房子的时候,肯定不会直接找想出租房子或者卖房子的人,通常情况下是去找中介来谈, 这个中介实则就是起到的一个代理作用。
代理实则分为三种: 缓存代理、虚拟代理、保护代理
(1)缓存代理
解释: 可以为一些开销大的运算结果提供暂时的存储,下一次运算时,如果传递过来的参数跟之前一致,则返回前面存储的运算结果
如下:
var add = function(){
let a =1
for(var i=0 ;i<arguments.length;i++){
a=a*arguments[i]
}
return a
}
var proxy = (function(){
// 用来记录传入的参数
var cache ={}
return function(){
let args = Array.prototype.join.call(arguments,',')
if(args in cache){
return cache[args]
}
return (cache[args] = add.apply(this,argusments))
}
})()
(2)虚拟代理
解释: 把一些开销很大的对象,延迟到真正需要它的时候才去创建
比如图片的预加载:
let MyImage =(function(){
let imgNode= document.createElement('img')
document.body.appendChild(imgNode)
let img =new Image()
img.onload =function(){
imgNode.src =img.src
}
return {
setSrc:function (src){
imgNode.src="load.jpg"
img.src = src
}
}
})()
MyImage.setSrc('http://xx.jpg')
(3)保护代理
解释:控制对一个对象的访问权限
例子,比如一个粉丝给他的偶像邮寄花,但实际上肯定都会被偶像的经纪人接收,由经纪人进行筛选,之后筛选通过后,才会再给明星
如:
const Fans ={
flower(){
Agent.receive('花朵')
}
}
const Agent ={
receive:function(gift){
if(gitf !=='Money'){
star.receive(gift)
}
}
}
const star={
receive:function(gift){
console.log('收到的礼物',gift)
}
}
5.发布订阅模式以及观察者模式
观察者模式 :定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
其中有两个角色,一个是观察者(Observer),一个是主题(Subject),观察者直接订阅了主题,而当主题被激活的时候,就会触发观察者里的事件
发布订阅模式 :有三个角色,一个是发布者,一个订阅者,一个调度中心,订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,之后又调度中心统一调度订阅者注册到调度中心的处理代码
举个例子吧,就是你去商店买某件商品,但老师告诉你现在商品没有货,于是你留下了自己的联系方式,等有货了通知你,之后货到了,商家就会给你发送消息
两者的区别
- 观察者模式中,观察者是知道subject的,彼此是相互知道对方存在的,然而在发布订阅模式中,发布者和订阅者是完全不知道对方存在的,是靠调度中心来进行连接的
- 在发布订阅模式中,是松散耦合,而观察者模式中,组件是紧耦合的
- 观察者模式大多数时候是同步的,而使用发布-订阅模式大多时候是异步的