闲言碎语
为什么要说设计模式呢?先来看看设计模式的定义:在面向对象软件设计过程中针对特定问题的简介而优雅的解决方案。emmm…其实我目前还没有这么优雅,作为一个前端小白菜,目前学习设计模式的目的就是为了多了解一些设计方法,好在以后读的源码的时候知道为什么要这样写
一.工厂模式
Java中常用的一种设计模式,这种类型的设计模式属于创建型
,提供了一种创建对象的最佳方式, 在工厂模式中,我们不需要对客户暴露对象的创建逻辑,而是将创建实例的具体内容封装起来
- 如果创建一个对象Editor的过程很复杂,需要传color,background,text等参数,而且很多地方都会用到,那么就可以把创建过程封装起来
- 比如我们在连接数据库,创建连接对象的时候,每次都要输入账号密码,那么就可以用工厂模式来帮我们创建
- 类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。(暂时没想到例子)
class Editor {
constructor(name, color, bgColor, ...others) {
// 这里的 ...others为了说明这个Editor类初始化的时候有很多参数
this.name = name;
this.color = color || 'red';
this.background = bgColor || '#7e7e7e';
Object.keys(others).forEach((key) => {
this[key] = others[key];
})
}
setName(name) {
this.name = name;
}
getName(name) {
return this.name;
}
// ...其他方法
}
class DBHelper {
constructor(userName, password, dbName) {
this.userName = userName;
this.password = password;
this.dbName = dbName;
}
//... 其他的一些方法
}
// 实际运用一般使用将其他对象用模块封装起来
class Factory {
create(name) {
switch (name) {
case 'Editor':
new Editor('WangEditor', 'red', '#fff', 'param1', 'param2')
case 'DBHelper':
new DBHelper('root', '123456', 'cese');
}
}
}
const factor = new Factory();
const editor = factor.create('Editor');
const dbConnect = factor.create('DBHelper');
二.单例模式
保证一个类只有一个实例,并提供一个访问他的全局访问点。实现思路:每次创建的是时候判断是否已经存在实例,如果有则直接返回实例,如果没有,则创建了并且返回。
- 比如弹框,登陆框只有一个,用单例模式去实现就很合适
- *我在想高德中的map,有没有利用单例模式的想法去实现,因为一般来说地图只有一张(个人想法)
class CreateMessage {
constructor(name) {
this.name = name;
this.getName();
}
getName() {
// alert(`假装这是一个封装过了的Mssaage! ${this.name}`)
}
}
// 使用代理实现单例模式
const proxyMode = (() => {
// 使用闭包将变量私有化,后面的的代码都会采用这种写法
var instance = null;
return function (name) {
if (!instance) {
instance = new CreateMessage(name);
}
return instance;
}
})()
const msgA = new proxyMode('msgA');
const msgB = new proxyMode('msgB');
console.log(msgA === msgB); // true
三. 策略模式
策略模式就是将算法的调用
和算法的实现
分离,策略模式中至少分为两部分。第一部分是一组策略类(可变),第二部分是环境类(不可变),策略类负责封装具体的算法进行计算,而环境类则负责接收客户的请求,并且委托给某一个策略类
- 一个系统需要动态的在几种算法中选择一种
const level = {
'S': (money) => {
return money * 1.5;
},
'A': (money) => {
return money * 1.4;
},
'B': (money) => {
return money * 1.2;
},
'C': (money) => {
return money * 0;
},
'D': (money) => {
return money * 0;
}
}
var caculateBudet = (evaluate, base) => {
return level[evaluate](base);
}
caculateBudet('B', 2400);
四.代理模式
官方的解释:是为一个对象提供一个代用品或者占位符,以便控制对它的访问,简单理解:Proxy可以理解成,在目标对象之前架设一层’拦截’,外界对该对象的访问,都必须先通过这层拦截
- 像我们单例模式中,我们去创建一个对象不是直接创建,而是通过一个中间人,先判断一些是否存在这个实例,如果有直接返回已存在
- 虚拟代理模式:某一个开销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建(例: 使用虚拟代理懒加载图片)
// **下面这个例子感觉没有很清晰**
var imgFunc = (() => {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: (src) => {
imgNode.src = src;
}
}
})();
var proxyImage = (() => {
var img = new Image();
// 这里的this指向img,this永远指向最后调用他的那个对象
img.onload = function () {
imgFunc.setSrc(this.src);
}
return {
setSrc: (src) => {
// 这个例子的核心就是,我们不直接加载图片,而是先再代理对象中加载loading图片,然后给img设置Image,当图片加载完成的时候,再调用真实对象的setSrc
imgFunc.setSrc('./loading.png');
setTimeout(() => {
img.src = src;
}, 2000)
}
}
})();
proxyImage.setSrc('./home.png');
五.中介者模式
通过一个中间者对象,其他所有相关对象都通过该对象来通信,而不是相互引用,当其中的一个对象发生改变,只需要通知中间者对象即可。好处: 可以接触对象直接的紧耦合关系。 比如我们现实生活中,飞机通信,只需要通过塔台作为一个中间者对象,而不需要和所有飞机都建立通信
var goods = {
'red|32G': 2,
'red|64g': 3,
'blue|32G': 6,
'blue|64G': 7,
}
// 中介者
var mediator = (function () {
var colorSelect = document.getElementById('colorSelect');
var memorySelect = document.getElementById('memeorySelect');
var numSelect = document.getElementById('numSelect');
return {
changed: function (obj) {
switch (obj) {
case colorSelect:
//TODO
break;
case memorySelect:
//TODO
break;
case numSelect:
//TODO
break;
}
},
}
})();
colorSelect.onchange = function () {
mediator.changed(this);
};
memorySelect.onchange = function () {
mediator.changed(this);
};
numSelect.onchange = function () {
mediator.changed(this);
};
六.装饰器模式(下面这个例子很赞!)
在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法
- 原有的方法保持不变,在原有的方法上再挂载其他方法来满足现有的需求。
- 函数的解耦,可以将函数拆分成多个可复用的函数,再将拆分出来的函数挂在到某个函数上,增强了复用性(像Python中就直接在函数前@validateLogin,就会在调用函数前执行validateLogin函数,很方便)
Function.prototype.before = function (beforefn) {
var self = this; //保存原函数引用
return function () { //返回包含了原函数和新函数的 '代理函数'
beforefn.apply(this, arguments); //执行新函数,修正this,重新指向window
return self.apply(this, arguments); //执行原函数
}
}
Function.prototype.after = function (afterfn) {
var self = this;
return function () {
// 这里就是先执行原函数,然后执行after中的函数,然后把原函数返回
// (为什么原函数的执行结果一定要返回? 可能函数是由return 值的)
var ret = self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
}
var func = function () {
console.log('normal func');
return 'success!'
}
//func1和func3为挂载函数
var func1 = function () {
console.log('func1');
}
var func2 = function() {
console.log('func2');
}
func = func.before(func1).after(func2);
var flag = func();
七.观察者模式
定义了一种一对多的关系,让观察者对象同时监听某一个主题对象,这个主题对象的状态要发生变化时就会通知所有的观察者对象使他们能够自动更新自己,当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式
- 发布订阅,在vue里面实现数据的双向绑定就是通过 发布订阅+数据劫持的方式(观察者模式 + 代理模式)
function MessageCenter() {
var messages = {}; // 所有的消息都在这里注册 数据结构类似{key: []}
// 注册
this.regist = (msgType) => {
if (Object.keys(messages).includes(msgType)) {
console.log(`${msgType}已经注册了`);
} else {
messages[msgType] = [];
}
}
// 订阅
this.subScribe = (msgType, fn) => {
if (Object.keys(messages).includes(msgType)) {
messages[msgType].push(fn);
} else {
console.log(`${msgType}还没有注册,不能订阅`);
}
}
// 发布
this.fire = (msgType, args) => {
if (Object.keys(messages).includes(msgType)) {
messages[msgType].forEach((fn) => {
fn({args, type: msgType});
})
} else {
console.log(`${msgType}还没有注册,不能发布`);
}
}
}
var msg = new MessageCenter();
msg.regist('type1');
// 这里体现了js的异步编程思想,第二个参数相当于就是回调函数(从异步编程的角度来看,发布/订阅,也可以理解为事件监听的升级版)
// 想了解更多js的异步编程,请移步 https://blog.csdn.net/qq_41022872/article/details/102734769
msg.subScribe('type1', (param) => {
console.log(`${param.type}发布了,携带数据${JSON.stringify(param.args)}`);
});
msg.fire('type1', {
name: 'zaoren',
age: '18'
})