设计模式

1,114 阅读5分钟
|- 工厂模式  
|- 单例模式
|- 原型模式
|- 装饰器模式
|- 代理模式
|- 策略模式(行为型)
|- 状态模式(行为型)
|- 观察者模式(行为型)
|- 迭代器(行为型)

设计模式原则:

开放封闭原则的内容:对拓展开放,对内修改封闭

工厂模式:

what ?

将创建对象的过程单独封装,这样的操作就是工厂模式

  • 抽象工厂-> 具体工厂-> 抽象产品 -> 具体产品
  • 抽象工厂是佐证“开放封闭原则”的良好素材
//工厂模式示例
function User(name, age, career, work) {
    this.name = name;
    this.age = age;
    this.career = career;
    this.work = work;
}

function Factory(name, age, career) {
   let work;
   switch(career) {
       case 'coder':
           work = ['写代码'];
           break;
       case 'productor':
           work = ['生产'];
           break;
       ...
       
   }
   return new User(name, age, career, work)
    
}

单例模式

what ?

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式
  • 唯一数据源 (SSOT)

how ?

  • eg: 在vuex中的应用:一个 Vue 实例只能对应一个 Store
Vue.use(Vuex); 只注入一次vue的实例只会install一次vuex插件,保证store的唯一性

demo

  • 利用静态static 方法可以创建单例模式
class SingleSong {
    show() { console.log('sing....') }
    static getInstance() {
        if(!SingleSong.instance) {
            SingleSong.instance = new SingleSong();
        }
        return SingleSong.instance;
    }
}
// 方法2: 利用闭包创建单例
SingleSong.getInstance = (function(){
    let instance = null;
    return function() {
        if(!instance) {
            instance = new SingleSong();
        }
        return instance;
    }
})()
  • 利用闭包创建一个单例模式modal

  const Modal = (function() {
    	let modal = null
    	return function() {
            if(!modal) {
            	modal = document.createElement('div')
            	modal.innerHTML = '您还未登录哦~'
            	modal.id = 'modal'
            	modal.style.display = 'none'
            	document.body.appendChild(modal)
            }
            return modal
    	}
    })()
    
    const modal = new Modal()
  • 面试题: 实现Storage,使得该对象为单例,基于 localStorage 进行封装。实现方法 setItem(key,value) 和 getItem(key)。
// 方法一:es6 
class Storage {
    static getIns() {
        if(!Storage.instance) {
            Storage.instance = new Storage();
        }
        return Storage.instance;
    }
    getItem(key) {
        return localStorage.getItem(key)
    }
    setItem(key, value) {
        localStrage.setItem(key, value)
    }
}
// 方法二:
function StorageBase() {}
Storage.prototype.getItem = function(key) { return localStorage.getItem(key)}
Storage.prototype.setItem = function(key, value) {  localStrage.setItem(key, value)}
const Storage = (function(){
    let instance = null;
    return function() {
        if(!instance) {
            instance = new StorageBase();
        }
        return instance;
    }
})()

原型模式

  • JavaScript 这门语言面向对象系统的根本

装饰器模式:

what ?

  • 装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求”
  • 实例是在我们的代码运行时动态生成的,而装饰器函数则是在编译阶段就执行了。所以说装饰器函数真正能触及到的,就只有类这个层面上的对象

how ?

  • HOC 、 redux、axios 中的应用
  • HOC (Higher Order Component) 即高阶组件。它是装饰器模式在 React 中的实践
// react 中的高阶组件:
import React, { Component } from 'react'

const BorderHoc = WrappedComponent => class extends Component {
  render() {
    return <div style={{ border: 'solid 1px red' }}>
      <WrappedComponent />
    </div>
  }
}
export default borderHoc

demo

// 定义打开按钮
class OpenButton {
    // 点击后展示弹框(旧逻辑)
    onClick() {
        const modal = new Modal()
    	modal.style.display = 'block'
    }
}

// 定义按钮对应的装饰器
class Decorator {
    // 将按钮实例传入
    // 重点:
    constructor(open_button) {
        this.open_button = open_button
    }
    
    onClick() {
        this.open_button.onClick()
        // “包装”了一层新逻辑
        this.changeButtonStatus()
    }
    
    changeButtonStatus() {
        this.changeButtonText()
        this.disableButton()
    }
    // 单一职责
    disableButton() {
        const btn =  document.getElementById('open')
        btn.setAttribute("disabled", true)
    }
    // 单一职责
    changeButtonText() {
        const btn = document.getElementById('open')
        btn.innerText = '快去登录'
    }
}
装饰器在redux中的应用:
 
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import action from './action.js';

function mapStateToProps(state){
    return state.app;
}
function mapDispatchToProps(dispatch) {
    return bindActionCreators(action, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps);

axios 中的适配器

  • axios本身就用到了我们的适配器模式
  • 这正是一个好的适配器的自我修养——把变化留给自己,把统一留给用户
// 用fetch封装一个HttpUtils:
export default class HttpUtils{
    // get请求
    static get(url)
        return new Promise((resolve, reject) => {
            fetch(url)
                .then(result => {
                    resolve(result);
                })
                .catch(err => {
                    reject(err);
                })
        })
    // post请求
    static post(url, data){
        return new Promise((resolve, reject) => {
            fetch(url, {
                method: 'POST',
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: this.changeData(data)
            })
            .then(response => response.json())
            .then(result => {
                resolve(result);
            })
            .catch(err => {
                reject(err);
            })
        }
    }
    // body请求体的格式化方法
      static changeData(obj) {
        var prop,
          str = ''
        var i = 0
        for (prop in obj) {
          if (!prop) {
            return
          }
          if (i == 0) {
            str += prop + '=' + obj[prop]
          } else {
            str += '&' + prop + '=' + obj[prop]
          }
          i++
        }
        return str
      }
}

代理模式

how ?

  • proxy, 缓存

Proxy

es6中的代理:new Proxy(obj, {
    get: function(){},
    set: function(){}
})

缓存代理:

function addAll(){
    let result = 0;
    const let = arguments.length;
    for(let i =0 ;i < len; i++) {
        result += arguments[i];
    }
    return result;
}

const proxyAddAll = (function(){
    const resultCache = {};
    return function(){
        //所有参数转换为字符串
        let args = Array.prototype.join.call(arguments, ',');
        if(args in resultCache) {
            return resultCache[args];
        }
        return resultCache[args] = addAll(...arguments);
    }
})()

行为型-策略模式:

  • 对算法的封装,不依赖于主体
// 定义一个询价处理器对象
const priceProcessor = {
  pre(originPrice) {
    if (originPrice >= 100) {
      return originPrice - 20;
    }
    return originPrice * 0.9;
  },
  onSale(originPrice) {
    if (originPrice >= 100) {
      return originPrice - 30;
    }
    return originPrice * 0.8;
  },
  back(originPrice) {
    if (originPrice >= 200) {
      return originPrice - 50;
    }
    return originPrice;
  },
  fresh(originPrice) {
    return originPrice * 0.5;
  },
};

行为型-状态模式:

状态模式主要解决的是当控制一个对象++状态的条件表达式过于复杂时++的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

  • 指责分离
  • 开放封闭
  • 主体关联
class CoffeeMaker {
  constructor() {
    // 这里略去咖啡机中与咖啡状态切换无关的一些初始化逻辑
  
    // 初始化状态,没有切换任何咖啡模式
    this.state = 'init';
    // 初始化牛奶的存储量
    this.leftMilk = '500ml';
  }
  stateToProcessor = {
    that: this, // 重点!!!
    american() {
      // 尝试在行为函数里拿到咖啡机实例的信息并输出
      console.log('咖啡机现在的牛奶存储量是:', this.that.leftMilk)
      console.log('我只吐黑咖啡');
    },
    latte() {
      this.american()
      console.log('加点奶');
    },
    vanillaLatte() {
      this.latte();
      console.log('再加香草糖浆');
    },
    mocha() {
      this.latte();
      console.log('再加巧克力');
    }
  }

  // 关注咖啡机状态切换函数
  changeState(state) {
    this.state = state;
    if (!this.stateToProcessor[state]) {
      return;
    }
    this.stateToProcessor[state]();
  }


  latteProcress() {
    this.americanProcess();
    console.log('加点奶');
  }

  vanillaLatteProcress() {
    this.latteProcress();
    console.log('再加香草糖浆');
  }

  mochaProcress() {
    this.latteProcress();
    console.log('再加巧克力');
  }
}

const mk = new CoffeeMaker();
mk.changeState('latte');

行为型:观察者模式

  • 角色划分 --> 状态变化 --> 发布者通知到订阅者,这就是观察者模式的“套路”
  • Event Bus/ Event Emitter

class EventEmitter {
    constructor(){
        // handlers是一个map,用于存储事件与回调之间的对应关系
        this.handlers = {}
    }
    on(eventName, cb) {
        if(!this.handlers[eventName]) {
            // 初始化事件为空数组
            this.handlers[eventName] = []
        }
        // 把回调函数推入目标事件的监听函数队列里去
        this.handler[eventName].push(cb);
    }
    // emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
    emit(eventName, ...args){
        if(this.handler[eventName]){
             // 如果有,则逐个调用队列里的回调函数
            this.handlers[eventName].forEach(callback => {
                callback(...args);
            })
        }
    }
    // 移除某个事件回调队列里的指定回调函数
    // 一个监听事件可以对应多个回调函数
    off(eventName, cb) {
        const cbs = this.handles[eventName];
        const index = cbs.indexOf(cb);
        if(index !== -1) {
            cbs.splice(index,1)
        }
    }
    // 为事件注册单次监听器
    once(eventName, cb) {
     // 对回调函数进行包装,使其执行完毕自动被移除
        const wrapper = (...args) => {
            cb.apply( ...args);
           // 注意!!
            this.off(eventName, warpper);
        }
        this.on(eventName, wrapper);
    }
    
}

  • 观察者模式是只有发布者和订阅者。
  • 发布-订阅模式是有中间机构——事件中心,实现了发布者和订阅者的完全解耦。

行为型:迭代器

  • forEach无法遍历类数组(dom节点数组)
  • 通用遍历方法:for...of...
  • es6:只要具备Symbol.iterator属性就可以被遍历。

书籍:

  • head first 设计模式
  • 设计模式:可复用面向对象软件的基础