设计模式
设计模式是一种在长时间的经验与错误中总结出来可复用的解决方案。
设计模式主要分为3类:
创建型设计模式:专注于处理对象的创建
Constructor构造器模式,Factory工厂模式,Singleton单例模式,builder生成器模式
结构型设计模式:对象间组合,建立对象之间的关系
Decorator装饰者模式,Facade外观模式,Flyweight享元模式,Adapter适配器模式,Proxy代理模式
行为设计模式:简化和改善对象间的通信
Mediator中介者模式,Observer观察者模式
常用的设计模式
1、构造函数模式
function CreatePerson(name, age, job) {
this.name = name
this.age = age
this.job = job
// 这种添加方式不推荐,因为每个方法都要在每个实例重新创建一遍,浪费性能,应该以 组合构造原型模式 构造函数
this.introduce = function () {
console.log(`my name is ${this.name}, ${this.age} year's old, my job is ${this.job}`)
}
}
var person = new CreatePerson('tom', 18, 'student')
person.introduce() // my name is tom, 18 year's old, my job is student
new 的作用过程:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此 this 指向这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
2、组合构造原型模式
function CreatePerson(name, age, job) {
this.name = name
this.age = age
this.job = job
}
CreatePerson.prototype.introduce = function () {
console.log(`my name is ${this.name}, ${this.age} year's old, my job is ${this.job}`)
}
var person = new CreatePerson('lucy', 18, 'student')
person.introduce() // my name is lucy, 18 year's old, my job is student
3、(发布订阅 + 单例) 模式
class Listen {
constructor() {
this.handles = {}
}
on(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = []
}
if (typeof handle == 'function') {
this.handles[eventType].push(handle)
} else {
throw new Error({
name: TypeError,
msg: 'has not handle'
})
}
return this
}
emit(eventType, ...args) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType].map(item => {
item.apply(null, args)
})
} else {
throw new Error({
name: TypeError,
msg: `${eventType} is not exsit`
})
}
return this
}
off(eventType, handle) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = this.handles[eventType].filter((item, i, arr) => {
return item !== handle
})
} else if (typeof handle != 'function') {
throw new Error({
name: TypeError,
msg: 'has not handle'
})
} else {
throw new Error({
name: TypeError,
msg: `${eventType} is not exsit`
})
}
return this
}
}
Listen.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new Listen()
}
return instance
}
})()
const callback = function () {
console.log('callback');
}
let fun = Listen.getInstance()
fun
.on('fun', callback)
.on('fun', (...args) => {
console.log(args.join(' '));
})
.emit('fun', 'i', 'love', 'you')
.off('fun', callback)
.emit('fun', 'i', 'hate', 'you')
4、观察者模式
一个目标对象维持着一系列依赖于它的对象,将有关状态的任何变更自动通知观察者们。在观察者模式中,观察者需要直接订阅目标对象,观察者与目标对象之间有一定的依赖关系。
有4个重要的概念
目标对象(被观察者):维护一组观察患者,提供管理观察者的方法。
观察者: 提供一个更新接口,用于收到通知时,进行更新
具体目标对象:代表具体的目标对象
具体观察者:代表具体的观察者
// 目标对象
class Subject {
constructor() {
// 观察者列表
this.observers = []
}
addObserver(observer) {
this.observers.push(observer)
}
removeObserver() {
this.observers.pop()
}
notify() {
this.observers.forEach(observer => {
observer.update()
})
}
}
// 观察者
class Observer {
constructor() { }
// 使用时会被具体update方法覆盖
update() {
console.log('init');
}
}
// 具体目标对象
class currentSubject extends Subject {
constructor() {
super()
}
// 其他自定义方法
dosomething() {
console.log('currentSubject change')
this.notify() // 通知观察者更新消息
}
}
// 具体观察者
class currentObserver extends Observer {
constructor() {
super()
}
// 重写 update
update() {
console.log('change!')
}
}
// 订阅
let curSubject = new currentSubject()
let curObserver = new currentObserver()
curSubject.addObserver(curObserver) // 目标对象去添加观察者
// 触发
curSubject.dosomething() // currentSubject change // change!
5、发布/订阅模式
发布订阅模式可以说是观察这模式的一种变体,一种实现。它使用一个主题/事件通道,介于发布者和订阅者之间,避免了发布者和订阅者之间的依赖关系。
class PubSub {
constructor() {
// 主题/事件通道
this.topics = {}
}
publish(topic, ...args) {
if (!this.topics[topic]) {
return
}
let subscribers = this.topics[topic]
subscribers.forEach(subscriber => {
subscriber.apply(null, args)
})
}
subscribe(topic, subscriber) {
if (!this.topics[topic]) {
this.topics[topic] = []
}
this.topics[topic].push(subscriber)
}
}
let pubsub = new PubSub()
const subscriber = (...args) => {
console.log(args.join(' '));
}
pubsub.subscribe('one', subscriber) // i like you
pubsub.publish('one', 'i', 'like', 'you')
6、工厂模式
工厂函数提供一个通用的接口来创建对象,我们可以指定我们希望创建的对象类型,我们通知工厂函数需要什么类型的对象并提供对应的数据,返回对应的实例。
class Car {
constructor(options) {
this.doors = options.doors || 4;
this.state = options.state || "brand new";
this.color = options.color || "silver";
}
}
class Truck {
constructor(options) {
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
this.color = options.color || "blue";
}
}
function vehicleFactory (options) {
if (options.type === 'car') {
return new Car(options)
} else {
return new Truck(options)
}
}
何时使用工厂模式
当我们的对象比较复杂的时候。
当我们需要根据不同情况创建不同对象实例的时候。
当我们需要创建许多相似对象的时候。
缺点
使用不当会增加程序的复杂度
7、 抽象工厂模式
抽象工厂模式,将对象的实现细节抽离出来。适用于需要和多种对象一起工作的场景。
class Truck {
constructor(options) {
this.state = options.state || "used";
this.wheelSize = options.wheelSize || "large";
this.color = options.color || "blue";
}
}
class Car {
constructor(options) {
this.doors = options.doors || 4;
this.state = options.state || "brand new";
this.color = options.color || "silver";
}
}
class AbstractFactory {
constructor() {
this.types = {}
}
registerFactory(type, factory) {
this.types[type] = factory
}
getInstance(type, args) {
let factory = this.types[type]
if (factory) {
return new factory(args)
}
}
}
let abstractFactory = new AbortController()
abstractFactory.registerFactory('car', Car)
abstractFactory.registerFactory('truck', Truck)
abstractFactory.getInstance('car', options)
abstractFactory.getInstance('truck', options)
8、 单例模式
单例模式限制一个类只有一个实例化对象。
class Obj(data) {
// ....
}
// 利用闭包实现单例模式,确保obj类只有一个实例
function singleton (data) {
var instance;
return function () {
if (!instance) {
instance = new Obj(data)
}
return instance
}
}
9、中介者模式
中介者模式就是提供一个中心点给系统不同组件之间进行通信,降低系统组件之间的耦合程度。
// 实现与发布/订阅模式类似
class Mediator {
constructor() {
this.topics = {}
this.subUid = -1
}
subscribe(topic, func) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
var token = (++this.subUid).toString();
this.topics[topic].push({
token: token,
func: func
});
return this;
}
publish(topic, args) {
if (!this.topics[topic]) {
return false;
}
let subscribers = this.topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
return this;
}
}
// 具体应用
var a = {
run: function (arg) {
console.log('a received ' + arg);
}
};
var b = {
run: function (arg) {
console.log('b received ' + arg);
}
};
let controller = new Mediator()
controller.subscribe('myTopic', function (t, arg) {
a.run(arg); // a received data
}).subscribe('myTopic', function (t, arg) {
b.run(arg); // b received data
});
// boss 发布消息
controller.publish('myTopic', 'data');
观察者模式和发布订阅模式专注于维护目标对象和观察者之间的关系,当主题对象发送变化时,通知所有对改主题感兴趣的观察者。而中介者模式的话,专注于限制对象的通信必须通过中介者来通信。两者都提倡松耦合。
10、发布/订阅 与 中介者 模式区别
// 发布订阅模式
class PubSub {
constructor() {
this.handleList = {}
}
sub(eventName, handle) {
if (!this.handleList[eventName]) {
this.handleList[eventName] = []
}
this.handleList[eventName].push(handle)
return this
}
pub(eventName, ...args) {
if (!this.handleList[eventName]) return
this.handleList[eventName].map(handle => {
handle.call(null, ...args)
})
return this
}
}
let pubsub = new PubSub()
const eat = (...foods) => { console.log(`i like eat ${foods}`) }
const someWhere = (...position) => { console.log(`i like eat in ${position}`) }
pubsub.sub('eat', eat).sub('eat', someWhere)
pubsub.pub('eat', 'banana', 'shanghai')
// i like eat banana,shanghai
// i like eat in banana,shanghai
// 中介者模式
class PubSub {
constructor() {
this.handleList = {}
}
sub(eventName, token, handle) {
if (!this.handleList[eventName]) {
this.handleList[eventName] = []
}
this.handleList[eventName].push({ handle, token })
return this
}
pub(eventName, token, ...args) {
if (!this.handleList[eventName]) return
this.handleList[eventName].map(handle => {
if (handle.token === token) handle.handle.call(null, ...args)
})
return this
}
}
let pubsub = new PubSub()
const eat = (...foods) => { console.log(`bob like eat ${foods}`) }
const someWhere = (...position) => { console.log(`lucy like eat in ${position}`) }
pubsub.sub('eat', 'bob', eat).sub('eat', 'lucy', someWhere)
pubsub.pub('eat', 'bob', 'banana').pub('eat', 'lucy', 'shanghai')
// bob like eat banana
// lucy like eat in shanghai
11、装饰者模式
装饰者模式,通过一个装饰类对现有动态添加行为,以及对原有行为进行装饰。
// o 为已有对象
var M20 = function(o){ // 这里定义一个装饰类
var str = '20多岁的时候,';
// o 是传入的对象,调用传入对象的方法,加以装饰
this.eat = function(){
return str + o.eat()+",肥得很!";
};
this.drink = function(){
return str + o.drink()+",就是个水桶!";
};
this.coding = function(){
return str + o.coding()+",代码又写得撇!";
};
}
alert(new M20(david).eat()); // 20多岁的时候,大卫是个大胖子,一天只晓得吃,肥得很!
alert(new M20(david).drink()); // 20多岁的时候,大卫除了吃就是喝,就是个水桶!
alert(new M20(david).coding()); // 20多岁的时候,写代码吧,大卫,代码又写得撇!
12、适配器模式
使用一个新的接口对现有的接口进行包装,处理数据与接口的不匹配。
function api (x1, x2, x3) {
console.log(x1 + x2 + x3); // 用console.log来模拟接口的相关操作
}
var data = {
a: '我',
b: '很',
c: '帅'
}
function adapterApi (o) {
// 通过适配器函数来调用目的api
api(o.a, o.b, o.c);
}
adapterApi(data); // 我很帅