37、设计模式
设计模式:针对特定的场景,在软件设计过程中常见的代码范式(通用解决方案,与语言无关)。
- 观察者模式(麦当劳点餐)
- 代理模式(花店送花)
- 装饰器:可以允许向一个对象添加新的功能,却不改变原有对象。比如:
- 给文件上传功能添加日志输出功能
- react中redux的
@connect
- 职责链模式
代理模式
- 特点:代理对象与真实对象有相同的行为
- 真实对象->代理对象->用户
- 花店送花:男孩让花店送花给女孩
//定义女孩对象
var Girl = function(name){
this.name = name;
}
var Boy = function(girl){
//女同学
this.girl = girl;
//送花
this.sendGift = function(gift){
console.log(this.girl.name+gift) //花店下订单,花店人去送花
}
}
//代理对象 花店员工
var ProxyObj = function(girl){
this.girl = girl; //需要知道女孩信息
this.sendGift = function(gift){
(new Boy(this.girl)).sendGift(gift) //替人送花;直接调用
}
}
//调用 不能看出是谁送花
var girl = new Girl('Vicky')
var proxy = new ProxyObj(girl)
proxy.sendGift("flowers")
- 代理模式的应用:图片懒加载
- 真实图片:大、加载慢;代理图片:小,加载快
//常用图片懒加载
window.onload = function(){
var myImage = (function(){ //自执行函数
var imgNode = document.createElement('img') //创建真实图片节点
document.body.appendChild(imgNode) //图片节点加到body
var img = new Image() //代理对象 先展示代理图片 接着拉取实际图片
img.onload = function(){ //真实图片加载完毕后触发
imgNode.src = this.src; //把真实图片url给真实图片节点;替换等待图片;this指向代理对象
}
return { //返回一个对象
setSrc:function(src){
imgNode.src = 'https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw'; //代理图片url给到真实对象;先展示等待图片
img.src = src; //把src给到代理对象
}
}
})()
//调用:把真实图片给到对象
myImage.setSrc("https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499"); //真实图片
}
使用代理模式重构图片懒加载
window.onload = function(){
var myImage = (function(){
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc:function(src){
imgNode.src = src;
}
}
})()
//代理对象:先展示等待图片,再拉取真实图片
var ProxyImage = (function(){
var img = new Image() //内存中的代理
img.onload = function(){ //加载完之后将触发这个方法
myImage.setSrc(this.src) //this指向img;将真实图片给真实对象展示
}
return {
setSrc:function(src){
myImage.setSrc('https://th.bing.com/th/id/R20d0cd9f696181bbead3b250782239fc?rik=SozVXnglW3zBxg&riu=http%3a%2f%2fbpic.588ku.com%2felement_pic%2f01%2f35%2f08%2f79573bd17084b13.jpg&ehk=UPH3Ji9JG4ZMqBzH9qKIJwuFG2R31XoHzvp4NkK5vXc%3d&risl=&pid=ImgRaw') //代理图片
img.src = src;
}
}
})()
//调用;直接给代理对象设置真实图片 ProxyImage.setSrc('https://himg.bdimg.com/sys/portrait/item/pp.1.d64a7775.reheWDHF3vrjPLK3n-YgKw.jpg?tt=1623053822499') //真实图片
}
观察者模式(发布订阅模式)
场景:双十一购买商品。购买者:观察者;商品:被观察者;商家(商品价格修改):发布者。发布者发布,关注着会收到改变。
观察者、被观察者、发布者
//观察者
var shopObj = {}
//商品列表 ['huawei':['订阅人1','订阅人2'],'apple':['订阅人3','订阅人2'],'oppo':['订阅人1']]
shopObj.list = []
//订阅;根据特定的商品推给特定人
shopObj.listen = function(goodskey,fn){ //商品订阅方法;fn订阅的行为; 建立商品和观察者的联系
if(!this.list[goodskey]){
this.list[goodskey] = []
}
shopObj.list[goodskey].push(fn) //往特定商品列表中添加订阅
}
shopObj.publish = function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
观察者模式优化
//订阅发布放一起;之后再初始化
var event = {
list:[],
listen:function(key,fn){
if(!this.list[key]){
this.list[skey] = []
}
shopObj.list[key].push(fn) //往特定商品列表中添加订阅
},
publish:function(){
var goodskey = arguments[0]
var fns = this.list[goodskey]
for(var i = 0,fn;fn = fns[i++];){
//执行订阅的fn
fn.apply(this,arguments)
}
}
}
//观察者对象初始化
var initEvent = function(obj){
for(var i in event){
obj[i] = event[i];
}
}
//定义空发布者
var shopObj = {} //空对象
initEvent(shopObj) //将我们定义的方法属性都给它
//用户1:添加订阅
shopObj.listen('huawei',function(brand,model){
console.log('user 1'+brand,model)
})
//用户2:添加订阅
shopObj.listen('apple',function(brand,model){
console.log('user 2'+brand,model)
})
//商家发布订阅
shopObj.publish('huawei','P40')
装饰器
- 给对象添加额外行为但是没有改变原有对象
- 简单装饰器的实现
class Circle{
draw(){
console.log('draw a circle')
}
}
//使用装饰器添加一个边框
class Decorator{
constructor(circle){
this.circle = circle;
}
draw(){
this.circle.draw() //圆自己绘制
this.setBorder() //增加绘制方法
}
setBorder(circle){ //装饰器方法
console.log('draw border')
}
}
var circle = new Circle()
var dec = new Decorator(circle)
dec.draw()
- 装饰器--注解形式
class Boy {
@run
speak(){
console.log('speak')
}
}
function run(target,key,descriptor){ //装饰器参数。
//target是Boy对象;key是被装饰的方法speak;descriptor是描述方法(writeable可写;enumerable可枚举,即是否可以for循环出来;configuerable可配置)
console.log('run')
}
var boy = new Boy()
boy.speak()
需要配置babel
,使用transform-decoarors-legacy
将@
转化成ES5代码;安装之后使用babel xxx.js -o /build/xxx.js
进行转化。
cnpm i -S babel-cli babel-preset-env babel-plugin-transform-decoarors-legacy
//babel-preset-env用于ES6转ES5
//babel-plugin-transform-decoarors-legacy用于注解翻译的
- 装饰器参数
- target 对象
- key被装饰的方法
- descriptor 描述对象
//给方法添加日志输出功能;不改变原有功能,添加新功能
class Math{
@log
add(a,b){
return a+b;
}
}
//日志装饰器
function log(target,name,descriptor){
var oldValue = descriptor.value; //descriptor.value是被修饰的方法,add
descriptor.value = function(){
console.log(`调用${name}`参数是`${arguments}`) //arguments是js的内置对象,包含所有实参的类数组
return oldValue.apply(target,arguments)
}
return descriptor;
}
var math = new Math();
math.value(1,3)
装饰器添加参数
//给方法添加日志输出功能,并且带上参数,加到a、b上;
class Math{
@log(100)
add(a,b){
return a+b;
}
}
//日志装饰器
function log(num){
return function log(target,name,descriptor){
var _num = num || 0;
var oldValue = descriptor.value;
descriptor.value = function(...args){
arg[0] += _num; //改变原有参数值
arg[1] += _num;
console.log(`调用${name}`参数是`${args}`)
return oldValue.apply(target,args)
}
return descriptor;
}
}
var math = new Math();
math.value(1,3)
职责链模式
- 场景:充值抽奖
开闭原则:原有代码对现有需求时关闭的,对扩展时开放的
充值500抽100rmb;充值200抽20rmb;否则无奖
function(orderType,isPay,count){ //订单类型;是否支付成功;金额
if(orderType === 1){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 2){ //200rmb
if(isPay){
console.log('中奖20rmb')
}else{
if(count>0){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
}else if(orderType === 3){
console.log('谢谢参与')
}
}
- 使用职责链模式重构
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
order200(orderType,isPay,count)
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
orderdefault(orderType,isPay,count)
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
- 使用职责链再次重构(不关自己的事情就交给下一个)
function order500(orderType,isPay,count){
if(orderType === 1 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function order200(orderType,isPay,count){
if(orderType === 2 && isPay){ //500rmb
if(isPay){
console.log('中奖100rmb')
}else{
//console.log('不关我的事,给下一个处理')
return "nextSuccessor"
}
}
function orderdefault(orderType,isPay,count){
if(count>0 && isPay){
console.log('抽到纪念奖')
}else{
console.log('谢谢参与')
}
}
//创建职责链关系对象
function Chain(fn){
this.fn = fn;
this.successor = null;
}
//设置下一个关系对象
Chain.prototype.setnextSuccessor =function(successor){
this.successor = successor;
}
//传递给下一个
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments)
if(ret === 'nextSuccessor'){ //传给下一个
this.successor && this.successor.passRequest.apply(this.successor,arguments)
}
}
//实例化
var chain500 = new Chain(order500)
var chain200 = new Chain(order200)
var chaindefault = new Chain(orderdefault)
chain500.setnextSuccessor = chain200;
chain200.setnextSuccessor = chaindefault;
//调用
chain500.passRequest(1,true,100) //类型1 支付成功 金额500
面向对象:抽象、多态、继承、封装