几个JS设计模式

42次阅读

共计 3295 个字符,预计需要花费 9 分钟才能阅读完成。

发布—订阅模式

发布订阅模式指的是希望接收通知的对象(Subscriber)基于一个主题通过自定义事件订阅主题,被激活事件的对象(Publisher)通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。

const fs = require('fs')
let events = {arr: [],
    on(callback){this.arr.push(callback)
    },
    emit(){this.arr.forEach(fn => fn())
    }
}

events.on(function(){console.log('订阅');        // 订阅事件
})

fs.readFile('./demo1.txt','utf8',function(err,data){events.emit();            // 发布
})

fs.readFile('./demo2.txt','utf8',function(err,data){events.emit();            // 发布
})

首先看一个简单的例子:定义一个对象 events,带有 arr 属性用于存放要执行的回调数组;on 和 emit 用于定义订阅和发布的方法。

events.on()定义了订阅方法,并且将要执行的函数存放到数组中去;events.emit()做发布行为,每次发布时都会让 events.on 中的回调执行一下,所以上面的代码执行结果就是:

// 订阅
// 订阅

下面再来修改一下回调的参数,在发布时将读取的 data 传递进去:

fs.readFile('./demo1.txt','utf8',function(err,data){events.emit(data);
})

fs.readFile('./demo2.txt','utf8',function(err,data){events.emit(data);
})

在 events 中新增 dataSource 属性,存放数据源:

let events = {dataSource: [],
    arr: [],
    on(callback){this.arr.push(callback)
    },
    emit(data){this.dataSource.push(data);
        this.arr.forEach(fn => fn(this.dataSource))
    }
}

这样做的目的是在订阅时能自己控制数据:

events.on(function(data){if(data.length === 2){console.log('订阅',data);        // 订阅事件
    }
})

一般发布订阅用于解耦合,主要还是基于回调函数实现。

可以不订阅,只发布;发布和订阅是分开的,并且二者之间不产生关系。

全部代码如下:

const fs = require('fs')
let events = {dataSource: [],
    arr: [],
    on(callback){this.arr.push(callback)
    },
    emit(data){this.dataSource.push(data)
        this.arr.forEach(fn => fn(this.dataSource))
    }
}

events.on(function(data){console.log('订阅',data);        // 订阅事件
})

fs.readFile('./demo1.txt','utf8',function(err,data){events.emit(data);
})

fs.readFile('./demo2.txt','utf8',function(err,data){events.emit(data);
})

观察者模式

观察者模式就是存储状态变化,当被观察者状态发生改变时,向观察者逐一发出通知。

class Subject{        // 目标对象类
    constructor(name){
        this.name = name;
        this.state = 1;
        this.arr = [];}
    attach(){this.arr.push(o);
    }
    setState(state){
        this.state = state;
        this.arr.forEach(o => o.updateState(state))
    }
}
class Observer{        // 观察者类
    constructor(name){this.name = name;}
    updateState(state){console.log(this.name + '收到状态改变是:' + state)
    }
}

let s = new Subject();
let o1 = new Observer('观察者 1');
let o2 = new Observer('观察者 2');

s.attach(o1);        // 被观察的目标对象注册观察者
s.attach(o2);

s.setState(2);      // 通知所有的观察者,状态改变
// 输出结果
观察者 1 收到状态改变是:2
观察者 2 收到状态改变是:2

观察者和被观察者有关联存在;观察者要放到被观察者的数组中。

vue就是基于观察者模式,当某个数据一变化时,即通知视图更新;另外,观察者模式其实包含发布订阅模式。

工厂模式

工厂模式是用来创建对象的一种最常用的设计模式,不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。

简单工厂

let Factory = function(role){function Admin(){
        this.name = '超级管理员';
        this.authority = ['首页','产品列表','产品详情','用户管理','权限管理'];
    }
    function Manager(){
        this.name = '管理员';
        this.authority = ['首页','产品列表','产品详情','用户管理'];
    }
    function User(){
        this.name = '普通用户';
        this.authority = ['首页','产品列表','产品详情'];
    }
    switch(role){
        case 'Admin':
            return new Admin();
        case 'Manager':
            return new Manager();
        case 'User':
            return new User();
        default: 
            throw new Error('参数错误,请选择:Admin,Manager,User');
    }
}

let admin = Factory('Admin');
let manager = Factory('Manager');
let user = Factory('User');

通过对传递参数的判定,返回不同的对象实例,但是这三种实例的内部构造都很相似,因此还可以对其进行优化。

工厂方法

工厂方法的本意是将实际创建对象的工作推迟到子类中,这样使得核心类变为抽象类。

在工厂函数的原型中设置所有对象的构造函数,这样便于后期扩展。

let Factory = function (role) {
    // 安全模式创建工厂方法函数
    if (this instanceof Factory) {let f = new this[role]();
        return f;
    } else {return new Factory(role);
    }
}

// 工厂方法原型设置对象构造函数
Factory.prototype = {Admin: function () {
        this.name = '超级管理员';
        this.authority = ['首页', '产品列表', '产品详情', '用户管理', '权限管理'];
    },
    Manager: function () {
        this.name = '管理员';
        this.authority = ['首页', '产品列表', '产品详情', '用户管理'];
    },
    User: function User() {
        this.name = '普通用户';
        this.authority = ['首页', '产品列表', '产品详情'];
    }
}

let admin = Factory('Admin');
let manager = Factory('Manager');
let user = Factory('User');

在上述调用函数过程中,一旦在任何阶段忘记使用 new 运算符,那么就无法获取到实例,但是使用安全模式进行实例化,就能很好地解决这个问题。

正文完
 0