可扩展型的设计模式(适配器模式-结构性;装饰者模式-结构性;命令模式-行为型;观察者模式-行为型;职责链模式-行为型;访问者模式-技巧型。)

  • 什么是好的可扩展性

  1. 需求发生变更时不需要重新改旧代码
  2. 代码修改不会引起大规模变动
  3. 方便加入新模块
  4. 低耦合

 

适配器模式

ps:结构性 (接口)

目的:通过写一个适配器,来代替替换

应用场景:面临接口不通用的问题

一:基本结构

用log代替console.log 

var log = (function(){
	return window.console.log
})()
ps:把原方法赋予新接口名(新方法)

二:示例

例子:框架的变更

需求:目前项目使用A框架,现在改成了B,两个框架十分类似,但是少数几个方法不同,

// 假设原框架的方法和新框架的方法是
A.c(); //$.css()
A.o(); //$.on()

window.A=$;
A.c = function(){
	return $.css.apply(this.arguments);
}
A.o = function(){
	return $.on.apply(this.arguments);
}

例子:参数适配

需求:为了避免参数不适配产生的问题,很多框架会有一个参数适配操作,

function f1(config){
	// 默认配置
	var _default={
		name:'xxx',
		color:'red'
	}
	// 用户不传就用默认配置
	for(var item in _default){
		_default[item] = config[item] || _default[item]
	}
}
ps:当你的方法传入的参数较为复杂,最好都加上参数适配器

 

装饰者模式

ps:结构性 (方法内容)

目的:不重写方法的扩展方法

应用场景:当一个方法需要扩展但又不好去修改他的方法

一:基本结构

假设有个人写了个a模块,内部有个b方法,模块为他人写好,不能修改,如何扩展b方法呢

var a = {
	b:function(){
		
	}
};
function myb(){
	a.b();
	// 然后继续书写扩展方法
}
ps:我们新建一个自己的方法,在内部调用b方法,并且再执行自己的方法,这样可不在修改原对象的情况下,去扩展

二:示例

例子:扩展已有的事件绑定

需求:现在项目改造,需要给input标签已有的事件增加操作

// 新建一个适配工厂,接受两个参数,一个是绑定的dom,一个是要扩展方法
var decorator = function(dom,fn){
	// 判断dom上是否已经绑定click事件
	if(typeof dom.onclick == "function"){
		// 拿老方法
		var _old = dom.onclick;
		// 再绑定click事件
		dom.onclick = function(){
			//调用老方法和扩展方法
			_old();
			fn();
		}
	}
}
decorator(document.getElementById('dom1'),function(){
	// 我需要扩展的方法具体实现
	console.log(2);
})
ps:没必要去找到老代码在哪去改写,直接传入我需要改动的dom和扩展fn

例子:Vue的数组监听

需求:Vue中利用definePropert可以监听对象,那数组怎么办

// 又不能去改写数组原有的方法,先将数组原有的方法拷贝一份
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);

// 定义哪些方法会触发数组更新
var methodsToPatch = [
	'push',
	'pop',
	'shift',
	'unshift',
	'splice',
	'sort',
	'reverse'
];
methodsToPatch.forEach((method)=>{
	// 先拿出老方法
	var original = arrayProto[method];
	// 调用老方法
	var result = original.apply(this,arguments);
	// 加上我们要调用的新方法
	dep.notify();
	
	return result;
})

命令模式

ps:行为型

目的:解耦实现和调用,让双方互不干扰,

应用场景:调用的命令充满不确定性

一:基本结构

var command = (function(){
	var action = {
		// 这里是具体的实现
	};
	return function excute(){
		// 这里是供外部调用的方法,在此可以拿到action的实现
	}
})();
ps:调用的时候只需要给到command命令,excute去解析命令并调用具体的实现

二:示例

例子:绘图命令

需求:封装一个canvas绘图库

var canvasCommand = (function(){
	var action = {
		drawCircle:function(){
			
		},
		drawRect:function(){
			
		}
	}
	return function excute (commander){
		commander.forEach((item)=>{
			action[item.command](item.config);
		})
	}
})();
// 调用只需要传入需要绘制的图形数组
canvasCommand([
	{
		command:'drawRect',
		config:{},
	},{
		command:'drawRect',
		config:{},
	}
])

例子:绘制随机数量的图片

需求:需要做一个画廊,图片数量和排列顺序随机

// 创建图片html=>展示
var createImg = (function(){
	var action = {
		create:function(){
			var htmlArr=[];
			var _htmlstring = '';
			var _htmlTemplate="

{{title}}

"; //行为型-->策略模式,图片排序方式 var displayWay={ normal:function(arr){ return arr; }, reverse:function(arr){ return arr.reverse; } } obj.imgArr.forEach((img)=>{ var _html; _html=_htmlTemplate.replace('{{img-url}}',img.img).replace('{{title}}',img.title); htmlArr.push(_html); }) htmlArr=displayWay[obj.type](htmlArr); _htmlstring=htmlArr.join(""); return "
"+_htmlstring+"
"; }, display:function(obj){ var _html=this.create(obj); obj.target.innerHTML=_html; } }; return function excute (obj){ // 结构性-->适配器模式,obj可能极其复杂,先适配器默认参数 var _default = { imgArr:[{img:'xxx',title:'default title'}], type:'normal', target.document.body }; for(var item in _default){ _default[item] = obj[item] || _default[item]; }; action.display(_default); } })() createImg({ imgArr:[{img:'xxxx',title:'default title1'},{img:'xxxx',title:'default title2'}], type:'normal', }) ps: 用户只管输入命令,不用关心api , 命令和实现解耦 数据-->直接调用api 变成 数据-->excute命令解析层-->调用api

观察者模式

ps:行为型 (事件绑定)

目的:减少对象间的耦合,来提高扩展性

应用场景:当两个模块直接沟通会增加他们的耦合时

一:基本结构(模式不是固定的,需要记住的是思维)

// 定义一个观察者
function observe(){
	this.message={
		// 这个对象储存我监听的事件
	}
}
// 注册事件
observe.prototype.regist = function(type,fn){
	this.message[type] = fn;
}
// 执行事件
observe.prototype.fire = function(type,fn){
	this.message[type]();
}
// 删除事件
observe.prototype.remove = function(type,fn){
	this.message[type] = null;
}
ps:定义一个中转观察者,两个模块之间不直接沟通,而是通过观察者 一般适用于不方便直接沟通,或者异步操作

 

二:示例

例子:多人合作问题

需求:现在假设A工程师写了首页模块,B工程师写了评论模块.现在要把评论展示在首页

// 定义一个观察者
function observe(){
	this.message = {};
}
// 注册事件
observe.prototype.regist = function(type,fn){
	this.message[type] = fn;
}
// 触发
observe.prototype.fire = function(type,fn){
	this.message[type]()
}

var observeOb = new observe();

// 假设首页需要热评
function comment(){
	this.commentList = [
		{
			type:'hot',
			content:'xxxx'
		}
	];
	// 注册事件,把需要的数据给出去
	var self = this;
	observeOb.regist('getHot',function(){
		var _arr = [];
		self.commentList.forEach((item)=>{
			if(item.type=='hot'){
				_arr.push(item)
			}
		})
		return _arr;
	})
}

function index(){
	// 执行注册的事件就可以拿到需要的数据
	var _arr = observeOb.fire('getHot');
}

例子:一个转盘

需求:一个转盘应用,每转一圈速度会变慢

// 定义一个观察者
function observe(){
	this.message={
		// 这个对象储存我监听的事件
	}
}
// 注册事件
observe.prototype.regist = function(type,fn){
	this.message[type] = fn;
}
// 执行事件
observe.prototype.fire = function(type,fn){
	this.message[type]();
}
// 删除事件
observe.prototype.remove = function(type,fn){
	this.message[type] = null;
}
// 初始化html=>最终结果选定=>运动效果=>运动控制
var _domArr = [];
function htmlInit(target){
	// 初始化10个奖品 
	for(var i=0;i<9;i++){
		// 每次循环创建一个div
		var _div = document.createElement('div');
		_div.innerHTML = i;
		// 初始样式
		_div.setAttribute('class','item');
		// 放进target
		target.appendChild(_div);
		_domArr.push(_div);
	}
}
// 结果模块
function getFinal(){
	var _num = math.random()*10+40;//基础转四圈,停留在哪一个用随机数
	return Math.floor(_num,0);
}
// 动画模块
//moverConfig={moveTime:10移动次数(几格),speed:50多少ms移动一次}
function mover(moverConfig){
	var nowIn = 0;//当前抽奖效果在哪
	var removeNum = 9;//当前需要删除的效果
	var timer = setInterval(()=>{
		if(nowIn!=0){//如果现在不是第一个
			// 那么需要删除的效果就是当前效果的前一个
			removeNum = nowIn-1;
		}
		// 删除和添加效果代码
		_domArr[removeNum].setAttribute('class','item');
		_domArr[nowIn].setAttribute('class','item item-on');
		// 每次操作完后效果所属index加一
		nowIn++;
		if(nowIn == moverConfig.moveTime){//当移动次数等于需要移动次数
			clearInterval(timer);//停止
			if(moverConfig.moveTime == 10){//如果还在前4圈
				observeOb.fire('finish')
			}
		}
	},moverConfig.speed)
}
// 动画控制模块
function moveControll(){
	var final = getFinal();
	var _circle = Math.floor(final/10,0);//拿到一共跑的圈数
	var stopNum = final%10;//最终停留
	var _speed = 2000;//初始化速度
	var _runCircle = 0;//已经跑的圈数
	
	// 初始化开始运动
	mover({
		moveTime:10,speed:_speed
	})
	
	//注册监听
	observeOb.regist('finish',function(){
		var _time = 0;
		// 每次完成就减慢50ms
		_speed -= 50;
		// 完成圈数+1
		_runCircle ++;
		// 跑的小于等于总圈数
		if(_runCircle<=_circle){
			// 还没跑完,就每次还得跑十次
			_time = 10;
		}else{
			// 跑完就等于最终停留
			_time = stopNum;
		}
		mover({
			moveTime:_time,
			speed:_speed
		})
	})
}
htmlInit(document.getElementById('app'));
moveControll();

职责链模式

ps:行为型

目的:为了避免请求发送者与多个请求处理者耦合在一起,形成一个链条,

应用场景:把操作分割成一系列模块,每个模块只处理自己的事情,

一:基本结构(代码模式不是固定的,需要记住的是思维)

function m1(){
	
}
function m2(){
	
}
function m3(){
	
}
_result = m1(_result);
_result = m2(_result);
_result = m3(_result);
ps:把要做的事情组织成一条有序的链条,通过在这条链条传递消息来完成功能.适用于不涉及到复杂异步的操作

二:示例

例子:Axios请求拦截器

需求:axios拦截器的设计,可以看出是一个职责链思想去处理请求

// axios拦截器
// 请求拦截器 => 请求 => 响应拦截器

//首先有axios方法
function axios(){
    //拦截器
	this.interceptors = {
		request:new interceptorsManner(),
		response:new interceptorsManner()
	}
}
//定义请求部分,假设为request
axios.prototype.request = function(){
	//dispathRequest这个方法才是真正的发请求出去,这里不写了,知道就好
	var chain = [dispathRequest,undefined];
	// 拿到状态promise
	var promise = Promise.resolve(config);
	// 在使用拦截时候,拿到请求拦截添加到数组前面中去,依次调用
	this.interceptors.request.handlers.forEach((interceptors)=>{
		chain.unshift(interceptors.fulfilled,interceptors.rejected);
	})
	// 响应拦截放后面
	this.interceptors.response.handlers.forEach((interceptors)=>{
		chain.push(interceptors.fulfilled,interceptors.rejected);
	})
	
	while(chain.length){//循环,每次去拿出成功的和失败的执行
		promise = promise.then(chain.shift(),chain.shift())
	}
}

//拦截器类
function interceptorsManner(){
	this.handlers = [];
}
// use的时候往handlers中添加请求拦截和响应拦截
interceptorsManner.prototype.use = function(fulfilled,rejected){
	this.handlers.push({
		fulfilled:fulfilled,
		rejected:rejected
	})
}

例子:利用职责链组织一个表单验证

需求:有一个表单,需要先前端验证,再后端验证

// 表单事件绑定=>前端验证=>后端验证

input.onblur = function(){
	// 我们简单定义一下:假设input.value拿到value值(实际上不可能)
	var _value = input.value;
	var _arr = [font,back];
	
	// 定义一个同步函数
	async function test(){
		// 初始化值就等于value
		var _result = value;
		while(_arr.length>0){//当链中还有时候
			_result = await _arr.shift()(_result);
		}
		return _result;
	}
	// async拿到的是一个pomise
	text().then((res)=>{
		console.log(res)
	})
}

// 前端验证 同步
function font(result){
	
}

// 后端验证 异步
function back(){
	
}
ps:要做的事情拆分成模块,模块之间只做自己模块的事情,然后传递结果

访问者模式

ps:技巧型

目的:解耦数据结构与数据操作,

应用场景:数据结构不希望与操作关联,适用于操作对象或者操作数据可能会变,操作对象不必要知道操作数据,相反同理

一:基本结构

// 我们要操作的data
var data=[];
var handler = function (){
	
}
handler.prototype.get = function(){
	
}
var vistor = function(handler,data){
	handler.get(data);
}
ps:定义一个访问者,代替直接访问对象,来减少两个对象之间的耦合,

二:示例
例子:不同角色访问数据

需求:假设有一个公司的财务报表 财务关心支出收入 老板关系盈利,

// 报表
function report(){
	this.income = '';//收入
	this.cost = '';//支出
	this.profit = '';//利润
}

function boss(){
	
}
boss.prototype.get = function(data){
	
}

function account(){
	
}
account.prototype.get = function(num1,num2){
	
}

// 设置一个访问者
function vistor(data,man){
	var handle = {
		boss:function(data){
			man.get(data.profit);
		},
		account:function(data){
			account.get(data.income,data.cost)
		},
	}
	// 拿到类的原名字
	handle[man.constructor.name](data);
}

vistor(new report(),new boss());

例子:表格操作

需求:一个可以新增,删除的表格,

function table(){
	
}
table.prototype.show = function(){
	
}
table.prototype.delete = function(){
	vistor(this,tableDat,'delete',id)
}
table.prototype.add = function(){
	
}
var tableData = [
	{
		id:1,
		name:'xxx',
		price:'xxx'
	}
]

function vistor(table,data,handle){
	var handleOb = {
		delete:function(id){
			
		},
		add:function(id,name,price){
			
		},
	}
	// arguments是类数组,先变成真正的数组
	var arg = Array.prototype.splice(arguments);
	// 先把前三项,也就是table,data,handle删除掉,剩下的就是你写入的参数了
	arg.splice(0,3);
	handleOb[handle].apply(this,arg);
}

 

 

 

 

 

 

 

你可能感兴趣的:(设计模式,JavaScript)