> 文档中心 > 常见的设计模式

常见的设计模式

目录

设计原则

单例模式

代码实现

工厂模式

工厂模式介绍

代码实现

原型模式

原型模式介绍

代码实现

适配器模式

适配器模式介绍

代码实现

小结

代理模式

代理模式介绍

代码实现

小结

策略模式

策略模式介绍

代码实现

迭代器模式

迭代器模式介绍

模式特点

代码实现

观察者模式

观察者模式介绍

发布订阅模式的构成

代码实现

命令模式

命令模式介绍

代码实现

状态模式

状态模式介绍

代码实现

小结


设计原则

  1. 开闭原则:告诉我们要【对扩展开放,对修改关闭】
  2. 里氏替换原则:告诉我们【不要破坏继承体系】
  3. 依赖倒置原则:告诉我们要【面向接口编程】
  4. 单一职责原则:告诉我们实现【类】要【职责单一】
  5. 接口隔离原则:告诉我们在设计【接口】的时候要【精简单一】
  6. 迪米特法则:告诉我们要【降低耦合度】

单例模式

这种设计模式的思想是确保一个类只有唯一实例,一般用于全局缓存,比如全局window,唯一登录浮窗等。采用闭包的方式实现如下

代码实现

在JavaScript里,实现单例的方式有很多种,其中最简单的一个方式是使用对象字面量的方法,其字面量里可以包含大量的属性和方法:

var mySingleton = {    property1: "something",    property2: "something else",    method1: function () { console.log('hello world');    }};
var single = (function () {    let instance;    function getInstance() { // 如果该实例存在,则直接返回,否则就对其实例化 if (instance === undefined) {     instance = new Construct(); } return instance    }    function Construct() { // ... 生成单例的构造函数的代码 return{     name:"iwen",     getName(){  return this.name;     } }    }    return { getInstance: getInstance    }})();console.log(single.getInstance().getName());
        class Login { createLayout() {     var oDiv = document.createElement('div')     oDiv.innerHTML = '我是登录框'     document.body.appendChild(oDiv)     oDiv.style.display = 'none'     return oDiv }    }    class Single { getSingle(fn) {     var result;     return function() {  return result || (result = fn.apply(this, arguments))     } }    }    var oBtn = document.getElementById('btn')    var single = new Single()    var login = new Login()    // 由于闭包,createLoginLayer对result的引用,所以当single.getSingle函数执行完之后,内存中并不会销毁result。    // 当第二次以后点击按钮,根据createLoginLayer函数的作用域链中已经包含了result,所以直接返回result    var createLoginLayer = single.getSingle(login.createLayout)    oBtn.onclick = function() { var layout = createLoginLayer() layout.style.display = 'block'    }

工厂模式

工厂模式介绍

工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,用工厂方法代替new操作的一种模式

代码实现

class Animal {    constructor(name) { this.name = name    }}class Creator {    create(name) { return new Animal(name)    }}var creator = new Creator()var duck = creator.create('Duck')console.log(duck.name) // Duckvar chicken = creator.create('Chicken') console.log(chicken.name) // Chicken
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')

原型模式

原型模式介绍

原型模式是指原型实例指向创建对象的种类,并通过拷贝这些原型创建新的对象,是一种用来创建对象的模式,也就是创建一个对象作为另一个对象的prototype属性

实现原型模式是在ECMAScript5中,提出的Object.create方法,使用现有的对象来提供新创建的对象的__proto__。

代码实现

     var lynkCoPrototype = {     model: '领克',     getModel: function () {  console.log("车辆技术:" + this.model);     } } var volvo = Object.create(lynkCoPrototype, {     model: {  value: '沃尔沃'     } }) volvo.getModel();//车辆技术:沃尔沃    

适配器模式

适配器模式介绍

适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。别称包装器(wrapper)。

代码实现

class GooleMap {    show() { console.log('渲染谷歌地图')    }}class BaiduMap {    show() { console.log('渲染百度地图')    }}function render(map) {    if (map.show instanceof Function) { map.show()    }}render(new GooleMap())  // 渲染谷歌地图render(new BaiduMap())  // 渲染百度地图

但是假如BaiduMap类的原型方法不叫show,而是叫display,这时候就可以使用适配器模式了,因为我们不能轻易的改变第三方的内容。在BaiduMap的基础上封装一层,对外暴露show方法。

class GooleMap {    show() { console.log('渲染谷歌地图')    }}class BaiduMap {    display() { console.log('渲染百度地图')    }}// 定义适配器类, 对BaiduMap类进行封装class BaiduMapAdapter {    show() { var baiduMap = new BaiduMap() return baiduMap.display()     }}function render(map) {    if (map.show instanceof Function) { map.show()    }}render(new GooleMap())  // 渲染谷歌地图render(new BaiduMapAdapter())  // 渲染百度地图

小结

  1. 适配器模式主要解决两个接口之间不匹配的问题,不会改变原有的接口,而是由一个对象对另一个对象的包装。
  2. 适配器模式符合开放封闭原则

 

代理模式

代理模式介绍

代理,顾名思义就是帮助别人做事,代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。

代理模式非常有意义,在生活中可以找到很多代理模式的场景,例如:明星都会有经纪人作为代理,如果想请明星做事情,要先找到他的经纪人,然后和经纪人把报酬和时间等细节都谈好了,再把商量好的合同给到明星签署。

代理模式多用于懒加载,合并请求和缓存等。

代码实现

class MyImage {    constructor() { this.img = new Image() document.body.appendChild(this.img)    }    setSrc(src) { this.img.src = src    }}class ProxyImage {    constructor() { this.proxyImage = new Image()    }    setSrc(src) { let myImageObj = new MyImage() myImageObj.img.src = 'file://xxx.png'  //为本地图片url this.proxyImage.src = src this.proxyImage.onload = function() {     myImageObj.img.src = src }    }}var proxyImage = new ProxyImage()proxyImage.setSrc('http://xxx.png') //服务器资源url

本例中,本体类中有自己的setSrc方法,如果有一天网络速度已经不需要预加载了,我们可以直接使用本体对象的setSrc方法,,并且不需要改动本体类的代码,而且可以删除代理类。

// 依旧可以满足需求var myImage = new MyImage()myImage.setSrc('http://xxx.png')

小结

代理模式符合开放封闭原则 本体对象和代理对象拥有相同的方法,在用户看来并不知道请求的本体对象还是代理对象

策略模式

策略模式介绍

策略模式的本意将算法的使用与算法的实现分离开来,避免多重判断调用哪些算法。适用于有多个判断分支的场景

代码实现

// 对于vip客户function vipPrice() {    this.discount = 0.5;}vipPrice.prototype.getPrice = function (price) {    return price * this.discount;}// 对于超级客户function superVipPrice() {    this.discount = 0.3;}superVipPrice.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(); var old = new superVipPrice();context.set('超级Vip客户', old, 200);context.getResult(); var Price = new Price();context.set('普通客户', Price, 200);context.getResult();  

迭代器模式

迭代器模式介绍

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

简单理解(白话理解):统一 “集合” 型数据结构的遍历接口,实现可循环遍历获取集合中各数据项(不关心数据项中的数据结构)

生活小栗子:清单 TodoList。每日清单有学习类、生活类、工作类、运动类等项目,清单列表只管罗列,不管类别。

模式特点

  1. 为遍历不同数据结构的 “集合” 提供统一的接口;
  2. 能遍历访问 “集合” 数据中的项,不关心项的数据结构

代码实现

var each = function (arr, callBack) {    for (let i = 0, len = arr.length; i < len; i++) { // 将值,索引返回给回调函数callBack处理 if (callBack(i, arr[i]) === false) {     break;  // 中止迭代器,跳出循环 }    }}// 外部调用each([1, 2, 3, 4, 5], function (index, value) {    console.log(index, value);})

观察者模式

观察者模式介绍

也叫发布订阅模式,在这种模式中,一个订阅者订阅发布者,当一个特定的事件发生的时候,发布者会通知(调用)所有的订阅者。

使用观察者模式的好处:

  1. 支持简单的广播通信,自动通知所有已经订阅过的对象。
  2. 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  3. 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

发布订阅模式的构成

最常见的发布订阅模式就是咱们DOM事件,仔细回想一下我们要给一个按钮,绑定一个事件,当我点击按钮的时候我要让他的颜色变了,并且页面弹出一个弹出框

我们分析一下这个流程

首先,我们得知道给哪个按钮的时候绑定事件,然后我们得知道触发事件以后需要干什么?

那么在这其中谁是发布者?

是DOM中的按钮,因为是在它身上绑定了事件,当我们点击按钮的时候它便向订阅者发布了这个消息

那么谁是订阅者?

是click事件,当点击按钮时,dom发布了一条消息,而事件订阅了它,所以当它被点击的时候,订阅者会接收到消息

代码实现

 

class Event {    constructor() { }    // 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)    handlers = {}    // 事件添加方法,参数有事件名和事件方法    addEventListener(type, handler) { // 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器 if (!(type in this.handlers)) {     this.handlers[type] = [] } // 将事件存入 this.handlers[type].push(handler)    }    // 触发事件两个参数(事件名,参数)    dispatchEvent(type, ...params) { // 若没有注册该事件则抛出错误 if (!(type in this.handlers)) {     return new Error('未注册该事件') } // 便利触发 this.handlers[type].forEach(handler => {     handler(...params) })    }    // 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)    removeEventListener(type, handler) { // 无效事件抛出 if (!(type in this.handlers)) {     return new Error('无效事件') } if (!handler) {     // 直接移除事件     delete this.handlers[type] } else {     const idx = this.handlers[type].findIndex(ele => ele === handler)     // 抛出异常事件     if (idx === undefined) {  return new Error('无该绑定事件')     }     // 移除事件     this.handlers[type].splice(idx, 1)     if (this.handlers[type].length === 0) {  delete this.handlers[type]     } }    }}var event = new Event() // 创建event实例// 定义一个自定义事件:"load"function load(params) {    console.log('load', params)}event.addEventListener('load', load)// 再定义一个load事件function load2(params) {    console.log('load2', params)}event.addEventListener('load', load2)// 触发该事件event.dispatchEvent('load', 'load事件触发')// 移除load2事件event.removeEventListener('load', load2)// 移除所有load事件event.removeEventListener('load')

命令模式

命令模式介绍

命令模式(Command)的定义是:用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说改模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。

代码实现

        cmd-demo    
var btn1 = document.getElementById('btn1') var btn2 = document.getElementById('btn2') // 定义一个命令发布者(执行者)的类 class Executor { setCommand(btn, command) { btn.onclick = function() { command.execute() } } } // 定义一个命令接收者 class Menu { refresh() { console.log('刷新菜单') } addSubMenu() { console.log('增加子菜单') } } // 定义一个刷新菜单的命令对象的类 class RefreshMenu { constructor(receiver) { // 命令对象与接收者关联 this.receiver = receiver } // 暴露出统一的接口给命令发布者Executor execute() { this.receiver.refresh() } } // 定义一个增加子菜单的命令对象的类 class AddSubMenu { constructor(receiver) { // 命令对象与接收者关联 this.receiver = receiver } // 暴露出统一的接口给命令发布者Executor execute() { this.receiver.addSubMenu() } } var menu = new Menu() var executor = new Executor() var refreshMenu = new RefreshMenu(menu) // 给按钮1添加刷新功能 executor.setCommand(btn1, refreshMenu) var addSubMenu = new AddSubMenu(menu) // 给按钮2添加增加子菜单功能 executor.setCommand(btn2, addSubMenu)

状态模式

状态模式介绍

状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。

代码实现

        state-demo         // 定义一个关闭状态的类    class OffLightState {     constructor(light) {  this.light = light     }     // 每个类都需要这个方法,在不同状态下按都需要触发这个方法     pressBtn() {  this.light.setState(this.light.weekLightState)  console.log('开启弱光')     } } // 定义一个弱光状态的类    class WeekLightState {     constructor(light) {  this.light = light     }     pressBtn() {  this.light.setState(this.light.strongLightState)  console.log('开启强光')     } } // 定义一个强光状态的类 class StrongLightState {     constructor(light) {  this.light = light     }     pressBtn() {  this.light.setState(this.light.offLightState)  console.log('关闭电灯')     } } class Light {     constructor() {  this.offLightState = new OffLightState(this)  this.weekLightState = new WeekLightState(this)  this.strongLightState = new StrongLightState(this)  this.currentState = null     }     setState(newState) {  this.currentState = newState     }     init() {  this.currentState = this.offLightState     } } let light = new Light() light.init() var btn = document.getElementById('btn') btn.onclick = function() {     light.currentState.pressBtn() }    

小结

  1. 通过定义不同的状态类,根据状态的改变而改变对象的行为,
  2. 不必把大量的逻辑都写在被操作对象的类中,而且容易增加新的状态
  3. 符合开放封闭原则