设计模式是为了更好的代码重用性,可读性,可靠性,可维护性。
1、适配器模式: 类似于生活中的转换器 让不兼容的东西兼容
相当于 我想要的不是你 但是把你转换成我想要的 这转换过程就是适配器模式
适配器模式可以看作一种“补偿模式”。
比如:
知道很多UI组件或者工具库会按指定的数据格式进行渲染,同一数据我们使用的地方有几个,格式不同。
可能功能1使用的数据格式是这样,需要将其一一对应列举起来,则接口返回格式:
[
{
"day": "周一",
"uv": 6300
},{
"day": "周二",
"uv": 7100
}, {
"day": "周三",
"uv": 4300
}, {
"day": "周四",
"uv": 3300
}, {
"day": "周五",
"uv": 8300
}, {
"day": "周六",
"uv": 9300
}, {
"day": "周日",
"uv": 11300
}
]
功能2处则Echarts图表图形需要的数据格式:
["周二", "周二", "周三", "周四", "周五", "周六", "周日"] //x轴的数据
[6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据
这时后端大哥不愿再提供接口,我们就需要适配器来解决问题:
//x轴适配器
function echartXAxisAdapter(res) {
return res.map(item => item.day);
}
//坐标点适配器
function echartDataAdapter(res) {
return res.map(item => item.uv);
}
还有我相当于引入一个插件库,我引入其中的一个方法,比如lodash里面的extend函数,
我需要将两个对象合并之后我再在对象的里面添加一个length属性来方便将这个对象通过array.from()转换成数组(上次分享的),则可以适配器来实现;
function extendLengthAdapter(obja, objb) {
let obj = _.extend(obja, objb);
obj.length = Object.keys(obj).length;
return obj;
}
vue的计算属性其实也是一种适配器思想,比如你玩游戏打赌输了,你需要把名字倒过来写,原来的输出你名字的接口不适用,则需要在原来的基础上适配出一个合适你的适配器。
//HTML代码
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
//javascirpt
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
//显示结果
Original message: "Hello"
Computed reversed message: "olleH"
2、状态模式
状态模式的使用场景也特别明确,有如下两点:
①一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
②一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示。
简而言之,当遇到很多同级if-else或者switch的时候,可以使用状态模式来进行简化
应用场景:灯泡状态、红绿灯切换等
常用的收藏功能
常规写法:
var Collection = function() {
this.state = 'noCollection'; // 给电灯设置初始状态 off
this.button = null; // 电灯开关按钮
};
Collection.prototype.init = function() {
var button = document.createElement('button'),
self = this;
button.innerHTML = '💗';
this.button = document.body.appendChild(button);
this.button.onclick = function() {
self.buttonWasPressed();
}
};
Collection.prototype.buttonWasPressed = function() {
if (this.state === 'noCollection') {
this.collection()
} else if (this.state === 'collectioned') {
this.cancel();
}
};
Collection.prototype.collection = function() {
this.state = 'collectioned';
console.log('收藏成功')
};
Collection.prototype.cancel = function() {
this.state = 'noCollection';
console.log('取消收藏成功')
};
let coll = new Collection();
coll.init();
使用状态模式修改:
借助于 JavaScript 的委托机制, 可以像如下实现状态模式:
const obj = {
'noCollection': {
press: function() { //把当前状态切换到下一个状态
console.log('收藏成功')
this.state = obj.collected;
}
},
'collected': {
press: function() {
console.log('取消收藏成功')
this.state = obj.noCollection;
}
},
}
const Collection = function() {
this.state = obj.noCollection;
}
Collection.prototype.init = function() {
const btn = document.createElement('button')
btn.innerHTML = '❤️';
document.body.append(btn)
const self = this
btn.addEventListener('click', function() {
self.currentState.press.call(self) // 通过 call 完成委托
})
}
const coll1 = new Collection()
coll1.init()
好处就是不需要进行多层判断,press函数封闭的,需要增加的时候不需要再去判断修改press函数
新增状态后的使用状态模式和不用状态模式的对比
普通版本新增:
Collection.prototype.buttonWasPressed = function() {
if (this.state === 'noCollection') {
this.collection()
} else if (this.state === 'collecting') {
this.collected();
}else if (this.state === 'collected') { // 新增两个状态
this.cancel();
}else if (this.state === 'canceling') {
this.canceled();
}
};
// 新增两个函数
Collection.prototype.collection = function() {
this.state = 'collecting';
console.log('收藏中。。。')
};
Collection.prototype.collected = function() {
this.state = 'collected';
console.log('收藏成功')
};
Collection.prototype.cancel = function() {
this.state = 'canceling';
console.log('取消收藏中。。。')
};
Collection.prototype.canceled = function() {
this.state = 'noCollection';
console.log('取消收藏成功')
};
let coll3 = new Collection();
coll3.init();
使用状态模式的新增:
const obj = {
'noCollection': {
press: function() { //把当前状态切换到下一个状态
console.log('收藏中。。。')
this.state = obj.collecting;
}
},
'collecting': {
press: function() { //把当前状态切换到下一个状态
console.log('收藏成功')
this.state = obj.collected;
}
},
'collected': {
press: function() {
console.log('取消收藏中。。。')
this.state = obj.canceling;
}
},
'canceling': {
press: function() {
console.log('取消收藏成功')
this.state = obj.noCollection;
}
},
}
普通写法:
~buttonWasPressed 方法是违反开放-封闭原则的,每次新增或者修改 light 的状态,都需要改动 buttonWasPressed 方法中的代码,这使得 buttonWasPressed 成为了一个非常不稳定的方法
~所有跟状态有关的行为,都被封装在 buttonWasPressed 方法里;如果以后增加状态和其他行为时,我们将无法预计这个方法将膨胀到什么地步,buttonWasPressed 将会很庞大且难以阅读和维护。
状态模式:
~解决了普通写法的问题
~使用面向对象的方式的话会写很多类,占用很大篇幅
~由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。
3、装饰器模式: 简单说就是在不改变自身的情况下 对原有的类进行功能增加和扩展
为对象添加新功能
不改变其原有的结构和功能
class Circle {
draw() {
console.log("画一个圆形");
}
}
class Decorator {
constructor(circle){
this.circle = circle;
}
draw() {
this.circle.draw();
console.log("设置红色边框")
}
}
// 测试代码
let circle = new Circle();
circle.draw()
let dec = new Decorator(circle);
dec.draw();
目前 TS 和 ES7 已经支持装饰器的使用,可以装饰类、方法,不能装饰函数
下面是新语法中方法装饰器的使用。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
target——装饰类的时候是类的构造函数,装饰类的方法时候是类的原型对象。(装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型)
name——成员的名字。
descriptor——成员的属性描述符。
// 定义border方法装饰器
let border = (target, name, descriptor) => {
const oldValue = descriptor.value
// 装饰器中替代原先的draw方法
descriptor.value = function () {
oldValue.apply(null, arguments)
//扩展
console.log(`${name}功能被增强`)
console.log("我能设置红色边框")
}
}
class Circle {
@border
draw(s: string) {
// 如果没有装饰器 直接执行这里
console.log(s);
}
}
let circle = new Circle();
circle.draw('i can draw circle');
输出:
i can draw circle
draw功能被增强
我能设置红色边框
4、观察者模式和发布订阅着模式
概念:
观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
class Subject {
constructor () {
this.state = 0;
this.observers = [];
}
getState () {
return this.state;
}
setState (state) {
this.state = state;
this.notify();
}
notify () {
this.observers.forEach(observer => {
observer.update();
})
}
attach (observer) {
this.observers.push(observer);
}
}
class Observer {
constructor (name, subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this);
}
update () {
console.log(`${this.name} update, state: ${this.subject.getState()}`);
}
}
let sub = new Subject();
let observer1 = new Observer('o1', sub);
let observer2 = new Observer('o2', sub);
sub.setState(1);
发布-订阅模式是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
发布-订阅模式实现思路
1、创建一个对象
2、在该对象上创建一个缓存列表(调度中心)
3、on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
4、emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
5、off 方法可以根据 event 值取消订阅(取消订阅)
6、once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)```
let eventEmitter = {
// 缓存列表
list: {},
// 订阅
on (event, fn) {
let _this = this;
// 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
// 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
(_this.list[event] || (_this.list[event] = [])).push(fn);
return _this;
},
// 监听一次
once (event, fn) {
// 先绑定,调用后删除
let _this = this;
function on () {
_this.off(event, on);
fn.apply(_this, arguments);
}
on.fn = fn;
_this.on(event, on);
return _this;
},
// 取消订阅
off (event, fn) {
let _this = this;
let fns = _this.list[event];
// 如果缓存列表中没有相应的 fn,返回false
if (!fns) return false;
if (!fn) {
// 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
fns && (fns.length = 0);
} else {
// 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
let cb;
for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
cb = fns[i];
if (cb === fn || cb.fn === fn) {
fns.splice(i, 1);
break
}
}
}
return _this;
},
// 发布
emit () {
let _this = this;
// 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
let event = [].shift.call(arguments),
fns = [..._this.list[event]];
// 如果缓存列表里没有 fn 就返回 false
if (!fns || fns.length === 0) {
return false;
}
// 遍历 event 值对应的缓存列表,依次执行 fn
fns.forEach(fn => {
fn.apply(_this, arguments);
});
return _this;
}
};
function user1 (content) {
console.log('用户1订阅了:', content);
}
function user2 (content) {
console.log('用户2订阅了:', content);
}
function user3 (content) {
console.log('用户3订阅了:', content);
}
function user4 (content) {
console.log('用户4订阅了:', content);
}
// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);
// 取消user2方法的订阅
eventEmitter.off('article1', user2);
eventEmitter.once('article2', user4)
// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
// eventEmitter.on('article1', user3).emit('article1', 'test111');
/*
用户1订阅了: Javascript 发布-订阅模式
用户3订阅了: Javascript 发布-订阅模式
用户1订阅了: Javascript 发布-订阅模式
用户3订阅了: Javascript 发布-订阅模式
用户4订阅了: Javascript 观察者模式
*/
区别:
1、在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
2、在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。