常见的设计模式总结

136 阅读5分钟

设计模式

设计模式是一个抽象的概念,是软件开发过程中的对于问题的一般解决思路

工厂模式

  • 定义:工厂模式是用来创建对象的一种常见的设计模式,不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数可以视为一个工厂。
  • 简单工厂模式:只需要在工厂函数中传入一个options,即可生成所需的实例
function Animal(options) {
    let obj = {};
    obj.color = options.color;
    obj.name = options.name;
    obj.getInfo = function () {
        return 'name :' + obj.name + ',color:' + obj.color;
    }
    return obj
}
let cat = Animal({ name: '小猫', color: 'white' });
console.log(cat.getInfo())
  • 工厂方法:抽象类只用于子类的继承,实例化对象的工厂继承抽象类,用于实例化对象
//定义一个抽象类,只用于子类的继承
class User {
    constructor(name = '', viewPage = '') {
        //通过new.target检测User是否通过new关键字被调用
        if (new.target === User) {
            throw new Error('抽象类不能实例化')
        }
        this.name = name;
        this.viewPage = viewPage;
    }
}
//定义一个实例化对象的工厂,只用于实例化对象
class UserFactory extends User {
    //继承抽象类中的参数
    constructor(name, viewPage) {
        super(name, viewPage)
    }
    //一个创建实例的方法,通过传入的不同参数创建不同的实例
    create(role) {
        switch (role) {
            case 'superAdmin':
                return new UserFactory('超级管理员', ['首页', '通讯录', '发现', '应用数据'])
            case 'admin':
                return new UserFactory('普通管理员', ['首页', '通讯录', '发现'])
            case 'user':
                return new UserFactory('普通用户', ['首页', '发现'])
            default:
                throw new Error('参数错误,可选参数:superAdmin,admin,user')

        }
    }
}
//通过工厂方法创建实例
let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
console.log(superAdmin)
  • 抽象工厂模式:不直接生成实例,而是用于对产品类的创建
//抽象工厂模式:不直接生成实例,而是用于对产品类的创建
function getAbstractUserFactory(type) {
    switch (type) {
        case 'wechat':
            return UserOfWechat;
        case 'QQ':
            return UserOfQQ;
        case 'weibo':
            return UserOfWeibo;
        default:
            throw new Error('参数错误')
    }
}
//生成不同的产品类
let WechatUserClass = getAbstractUserFactory('wechat')
let QQUserClass = getAbstractUserFactory('QQ');

//不同的用户实例
let wechatUser = new WechatUserClass('user1');

单例模式

  • 定义:保证一个类中仅有一个实例,并且能够提供一个全局访问点能够访问到这个实例。
  • 场景:
    • 例如windows回收站,任务管理器等,每次只能打开一个
    • 每次点击登陆按钮,不论点击多少次,只会出现一个登陆弹窗。
    • 防抖函数中每次只会设置一个定时器
    • Vue在安装插件的时候只能被安装一次,因为install方法中,进行了判断,如果已经有Vue实例,就不会重新install。
  • 使用静态类class实现单例模式
    • constructor中定义唯一实例的属性和方法
    • 提供一个全局访问点进行创建:通过静态类创建一个生成实力的方法,如果没有生成实例,就通过new关键字创建实例,如果有,就直接返回这个实例
//使用静态class实现单例模式
class SingleClass {
    constructor(name, creator, products) {
        this.name = name;
        this.creator = creator;
        this.products = products;
    }
    //通过静态类创建唯一的实例
    static getInstance(name, creator, products) {
        if (!this.instance) {
            this.instance = new SingleClass(name, creator, products)
        }
        return this.instance
    }
}
//通过instance创建实例,整个类只能被创建一个实例。
let apple = SingleClass.getInstance('apple', 'a', 'iphone');
let copy = SingleClass.getInstance('applecopy', 'b', 'bphone');
console.log(apple, copy)
  • 通过闭包实现单例模式
    • 创建一个single函数,把传入的函数变为只能执行一次的那种
    • 创建一个res指向null
    • return function,判断res是否是null?如果是就执行函数,并且执行结果赋值给res,不是就返回结果。
    • 因为内部函数引用了外层作用域的变量,因此变量res一直存在不会被回收,因此可以用来记录是否执行过函数。
//实现单例模式:通过闭包实现
    function Single(fn) {
        //闭包存储结果,下次调用时仍然保存在内存中,所以可以进行判断
        let res = null
        return function () {
            //如果没有res,就res=fn(),如果有就返回res
            return res == null ? (res = fn()) : res;
        } 
    }
    //函数:生成一个登陆弹窗
    function createLogin() {
        let div = document.createElement('div');
        div.innerHTML = '这是一个登陆弹窗,只出现一次';
        div.style.display = 'none';
        document.body.append(div);
        return div;
    }
    //将函数用Single包裹,使得这个函数只能生成一次
    console.log(Single(createLogin))

代理模式

  • 定义:使用一个代理对象,来操作对象中的属性。如果一个对象不适合直接引用另一个对象,代理对象可以在两个对象之间起到中介的作用。
  • 场景:例如Vue3实现响应式,就是利用Proxy实现的
  • 实现:ES6中的Proxy在目标对象之前多一层拦截,访问对象触发getter,增加或者修改对象触发setter,删除对象触发deleteProperty
let obj = {
    lxy:'lxy',
    hsh:'hsh',
}
let p = new Proxy(obj,{
    get(target,key,receiver){
        console.log(`访问到${key},${target}`);
        return Reflect.get(target,key,receiver)
    },
    set(target,key,value){
        console.log(`修改了${target},${key},${value}`)
        return Reflect.set(target,key,value)
    },
    deleteProperty(target,key){
        console.log('删除了'+target,key)
        return Reflect.deleteProperty(target,key)
    }
})
p.lxy
p.hsh = 'pig'
delete p.hsh
console.log(obj)

观察者模式

  • 定义:观察者模式定义了对象间一对多的依赖关系,当目标对象(被观察者)的状态发生改变时,所有依赖它的对象都会得到通知。
  • 使用场景:
    • 例如Vue实现响应式的依赖收集,然后当数据改变时,会通知所有的watcher去更新视图,就是利用观察者模式实现的。
  • 实现一个观察者模式
    • 创建两个类:一个是被观察者(目标对象),一个是观察者
    • 向被观察者身上添加一个观察者数组。并且创建一个方法用于向数组中添加观察者,一个方法用于从数组中移除观察者,一个方法用于通知所有观察者进行视图更新
    • 观察者有名称,以及一个进行更新的方法
//被观察者,目标类
class Subject{
    //向被观察者身上添加一个观察者数组
    constructor(){
        this.observers = []
    }
    //用于向观察者数组中添加观察者
    add(observer){
        this.observers.push(observer)

    }
    //用于从观察者数组中移除观察者
    remove(observer){
        const index = this.observers.findIndex(o=>o.name === observer.name)
        this.observers.splice(index,1);
    }
    //通知,执行所有observes的update方法
    notify(){
        const observers = this.observers;
        observers.forEach(observer=>{
            observer.update()
        })

    }
}
//观察者类
class Observer{
    constructor(name){
        this.name = name;
    }
    update(){
        console.log('去更新视图了啦~')
    }
}

//创建一个被观察者对象
let sub = new Subject()
let observer1= new Observer('lxy');
let observer2 = new Observer('hsh');
sub.add(observer1)
sub.add(observer2)
sub.notify()

发布订阅模式

  • 定义:发布订阅模式实现了对象间多对多的依赖关系,通过事件中心管理多个事件。发布者不会直接将消息发送给订阅者,而是将事件放入事件中心(消息队列)中,由订阅者接收感兴趣的事件。
  • 实现发布订阅模式
    • $on('事件名',fn(){回调函数}),意思是如果接收到了'事件名',就执行回调函数
    • $emit('事件名'),传递一个''事件'
    • $off('事件名'),移除这个事件
class Observer {
    constructor() {
        //一个消息队列,对象
        this.message = {}

    }
    //on方法接收一个事件名,一个回调函数,并且往消息队列中添加属性名为事件名,属性值为由回调函数组成的数组
    $on(type, fn) {
        //如果消息队列中有type事件名,就向里面添加事件
        //如果消息队列中没有事件名,就初始化一个数组
        if (!this.message[type]) {
            this.message[type] = [fn]
        } else {
            this.message[type].push(fn);
        }
    }
    $off(type, fn) {
        //如果没有type,return
        if (!this.message[type]) return
        //如果有type,没有fn,直接删除整个事件
        if (!fn) {
            this.message[type] = undefined;
            return
        }
        //如果有type,有fn,就只删除一个fn
        this.message[type] = this.message[type].filter((item) => item !== fn)
    }
    $emit(type, ...args) {
        //遍历消息队列,然后执行每一项
        //判断有没有
        if (!this.message[type]) return
        this.message[type].forEach(element => {
            element(...args);
        });
    }
    $once(type, fn) {
        function cb() {
            fn();
            this.$off(type, cb)
        }
        this.$on(type, cb);
    }
}

适配器模式

  • 定义:用于解决两个接口不兼容的情况,不需要改变已有的接口,而是通过给接口包装一层的方法实现两个接口兼容。
  • 场景:例如,百度地图和谷歌地图两个地图的渲染方法方法名不同,不能通过相同的渲染接口进行渲染,这时可以在不改变渲染方法的情况下,增加一个适配器接口
let googleMap = {
    show(){
        console.log('开始渲染谷歌地图')
    }
}
let baiduMap = {
    display(){
        console.log('开始渲染谷歌地图')
    }
}
//已有的渲染接口
function renderMap(map){
    if(map.show instanceof Function){
        map.show();
    }
}
renderMap(baiduMap);
//不能通过渲染接口调用baiduMap
renderMap(googleMap)
//在不改变地图接口的情况下,可以增加一个baiduMap的适配器接口
let baiduMapAdapter = {
    show(){
        return baiduMap.display();
    }
}
//这样就可以通过rendermap调用两个接口了

装饰模式

  • 定义:装饰模式不需要改变已有的接口,实现给对象添加额外的功能
  • 实现:使用ES7中的装饰器语法实现
function readonly(target,key,descriptor){
    descriptor.writable = false
    return descriptor;
}
class Test{
    @readonly
    name = 'lxy'
}
let t = new Test()
t.name = '111'