js实现各种设计模式 最全最详细
2019-12-27 本文已影响0人
thunderQin
简单工厂模式
- 定义: 又叫静态工厂方法,根据不同参数创建不同对象,并赋予属性和方法
- 应用:抽取类相同的属性和方法封装到对象
- 实例
let UserFactory = function (role) {
function User(opt) {
// 公共的属性和方法提取出来
this.name = opt.name;
this.viewPage = opt.viewPage;
}
// 根据不同参数返回不同参数
switch (role) {
case 'superAdmin':
return new User(superAdmin);
break;
case 'admin':
return new User(admin);
break;
case 'user':
return new User(user);
break;
default:
throw new Error('参数错误, 可选参数:superAdmin、admin、user')
}
}
//调用
let superAdmin = UserFactory('superAdmin');
let admin = UserFactory('admin')
let normalUser = UserFactory('user')
//最后得到角色,可以调用
工厂方法模式
- 定义: 对产品类的抽象使其创建业务主要负责用于创建多类产品的实例
- 应用: 创建实例
- 代码
function createPerson(name, age) {
var obj = {};
obj.name = name;
obj.age = age;
obj.writeJs = function () {
console.log(this.name + 'write js');
}
return obj;
}
var p1 = createPerson('mengzhe' , 26);
p1.writeJs();
var p2 = createPerson('iceman' , 25);
p2.writeJs();
抽象工厂模式和工厂模式的区别就是: 前者是通过参数返回不一样的实例,后者是直接new出来相应的实例
单例模式
- 定义: 一个实例只能被创建一次
- 应用: 提供命名空间
- 代码
let singleCase = function(name){
this.name = name;
};
singleCase.prototype.getName = function(){
return this.name;
}
// 获取实例对象
let getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {//相当于一个一次性阀门,只能实例化一次
instance = new singleCase(name);
}
return instance;
}
})();
// 测试单体模式的实例,所以one===two
let one = getInstance("one");
let two = getInstance("two");
外观模式
1. 定义: 为子系统中的一组接口提供一个一致的界面
- 应用: 简化复杂接口( stopPrevent )
- 比方:比如在家要看电影,需要打开音响,再打开投影仪,再打开播放器等等,引入外观角色之后,只需要调用“打开电影设备”方法就可以。外观角色封装了打开投影仪等操作,给使用者提供更容易使用的方法。
- 代码
function a(x){
// do something
}
function b(y){
// do something
}
function ab( x, y ){
a(x);
b(y);
}
适配器模式
- 定义:将一个接口转换成客户端需要的接口而不需要去修改客户端代码,使得不兼容的代码可以一起工作
- 应用:适配函数参数
- 代码
// 参数适配模拟eg1:
function add (x1, x2, x3) {
console.log(x1 + x2 + x3);
}
// 存在一个对象数据
var obj = {
a: '我',
b: '很',
c: '帅'
}
function adapter (o) {
// 通过适配器函数来调用目的api
add(o.a, o.b, o.c);
}
adapter(obj);
装饰者模式
- 定义:不改变原对象的基础上,给对象添加属性或方法
- 应用:@Form @connect 给类关联上对应的属性
- 代码
//获取事件源
let input=document.getElementById(input);
//若事件源已经绑定事件
if(typeof input.onclick=='function'){
//缓存事件源原有的回调函数
let oldClickFn=input.onclick;
//为事件源定义新事件
input.onclick=function(){
//事件源原有回调函数
oldClickFn();
//执行事件源新增回调函数
fn();
}
}else{
//未绑定绑定
input.onclick=fn;
}
}
//测试用例
decorator('textInp',function(){
console.log('文本框执行啦');
})
decorator('btn',function(){
console.log('按钮执行啦');
})
观察者模式
- 作用: 解决类与对象,对象与对象之间的耦合,两个组件之间的通信
- 场景: VUE的bus,onclick事件绑定
- 代码
let Observer=
(function(){
let _message={};
return {
//注册接口,
//1.作用:将订阅者注册的消息推入到消息队列
//2.参数:所以要传两个参数,消息类型和处理动作,
//3.消息不存在重新创建,存在将消息推入到执行方法
regist:function(type,fn){
//如果消息不存在,创建
if(typeof _message[type]==='undefined'){
_message[type]=[fn];
}else{
//将消息推入到消息的执行动作
_message[type].push(fn);
}
},
//发布信息接口
//1.作用:观察这发布消息将所有订阅的消息一次执行
//2.参数:消息类型和动作执行传递参数
//3.消息类型参数必须校验
fire:function(type,args){
//如果消息没有注册,则返回
if(!_message[type]) return;
//定义消息信息
var events={
type:type, //消息类型
args:args||{} //消息携带数据
},
i=0,
len=_message[type].length;
//遍历消息
for(;i<len;i++){
//依次执行注册消息
_message[type][i].call(this,events);
}
},
//移除信息接口
//1.作用:将订阅者注销消息从消息队列清除
//2.参数:消息类型和执行的动作
//3.消息参数校验
remove:function(type,fn){
//如果消息动作队列存在
if(_message[type] instanceof Array){
//从最后一个消息动作序遍历
var i=_message[type].length-1;
for(;i>=0;i--){
//如果存在该动作在消息队列中移除
_message[type][i]===fn&&_message[type].splice(i,1);
}
}
}
}
})()
//测试用例
//1.订阅消息
Observer.regist('test',function(e){
console.log(e.type,e.args.msg);
})
//2.发布消息
Observer.fire('test',{msg:'传递参数1'});
Observer.fire('test',{msg:'传递参数2'});
Observer.fire('test',{msg:'传递参数3'});
中介者模式
- 定义:设置一个中间层,处理对象之间的交互
- 代码:
var mediator = (function() {
var topics = {},
subUid = -1;
var publish = function(topic, args) {
if (!topics[topic]) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
return true;
};
var subscribe = function(topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
return {
publish: publish,
subscribe: subscribe,
installTo: function(obj) {
obj.publish = publish;
obj.subscribe = subscribe;
}
}
}());
// 具体应用
var mod1 = {
run: function(arg) {
console.log('mod1 received ' + arg);
}
};
var mod2 = {};
var topic = 'myTopic';
mediator.installTo(mod1);
mediator.installTo(mod2);
// mod1订阅消息
mod1.subscribe(topic, function(t, arg) {
mod1.run(arg);
});
// mod2发布消息
mod2.publish(topic, 'data');
中介者模式和观察者模式的区别就是
- 中介模式强调同事(colleague)之间的交互,需要知道所有同事的信息,相互影响(买房的时候,中介需要知道双方需求和信息)
- 观察者模式中,其实是发布和订阅模式,发布者不需要知道订阅的信息(收听广播,广播站只管发布,不需要接收订阅者信息)
访问者模式
- 定义:通过继承封装一些该数据类型不具备的属性,
- 作用:主要将稳定的数据结构和易变的操作分开,方便扩展多变的操作方法而保持稳定的数据结构
- 代码
// 定义奖金的访问者,在js中简单的用一个函数模拟,
// 如果在c#等强类型语言中,需要声明一个接口,不同的visitor实现不同的计算奖金方法,
// 比如下面的管理者和开发者奖金不一样
function bonusVisitor(employee) {
if (employee instanceof Manager)
employee.bonus = employee.salary * 2;
if (employee instanceof Developer)
employee.bonus = employee.salary;
}
// 定义员工类,注意继承此类的必须都带有accept这个接受visitor的方法,
// 就是用来接待访问者,进而内部用访问者调用自己方法实现一些操作,
// 此例中直接调用函数visitor(this)
class Employee {
constructor(salary) {
this.bonus = 0;
this.salary = salary;
}
accept(visitor) {
visitor(this);
}
}
// 管理者实现员工类
class Manager extends Employee {
constructor(salary) {
super(salary);
}
}
// 开发者实现员工类
class Developer extends Employee {
constructor(salary) {
super(salary);
}
}
使用
// 管理员工集合
let employees = [];
// 不同的员工
const john = new Developer(4000);
const christian = new Manager(10000);
// 放入员工集合中
employees.push(john);
employees.push(christian);
// 分别接收访问者并调用
employees.forEach(e => {
e.accept(bonusVisitor);
});
员工数据是稳定的,但是加薪的操作是不确定的,访问模式将两者分开。
状态模式
- 定义:一个对象状态改变会导致行为变化
- 作用:解决复杂的if判断
- 代码
var State = function(){
var States = {
state0 : function(params){
//your coding...
console.log('state0~');
},
state1 : function(params){
//your coding...
console.log('state1~');
},
state2 : function(params){
//your coding...
console.log('state2~');
},
state3 : function(params){
//your coding...
console.log('state3~');
},
};
function getResult(result){
States['state'+result] && States['state'+result]();
}
return{
setState : getResult
}
}();
State.setState(0);
注解:将if else转化为 map的过程 的过程
策略模式
- 定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户
- 作用: 把算法从业务中单独抽离出来
- 代码
var tween = {
linear: function(t, b, c, d){
return c*t/d + b;
},
easeIn: function(t, b, c, d){
return c * ( t /= d ) * t + b;
},
strongEaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d){
return c * ( ( t = t / d -1 ) * t * t * t * t + 1 ) + b;
},
sineaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t + b;
},
sineaseOut: function(t, b, c, d){
return c * ( ( t = t / d -1 ) * t * t + 1 ) +b;
}
};
/*
* 负责启动运动动画
*/
Animate.prototype.start = function( propertyName, endPos, duration, easing ){
this.startTime = +new Date; // 启动动画的时间
this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 节点的初始位置
this.propertyName = propertyName; // dom 节点需要被改变的 CSS 属性名
this.endPos = endPos; // dom 节点的目标位置
this.duration = duration; // 动画的持续时间
this.easing = tween[ easing ]; // 缓动算法
// 启动动画定时器
var self = this;
var timeId = setInterval(function(){
if( self.step() === false){
clearInterval(timeId);
}
},20);
};
桥接模式
- 定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化
- 代码
//bad
addEvent(Element,'click',getBeerById)
function getBeerById(e){
// 事件对象被作为参数传递给函数,而本例并没有使用这个参数,只是从this对象中获取id
var id = this.id;
asyncRequest('GET',`beer.uri?id=${id}`,function (res) {
console.log(`Request Beer:${res.responseText}`)
})
}
// good
function getBeerById(id,callback) {
asyncRequest('GET',`beer.uri?id=${id}`,function (res) {
// 回调 传入返回值
callback(res.responseText)
})
}
addEvent(Element,'click',getBeerByIdBridge)
function getBeerByIdBridge(e){
// 把id作为参数传递给getBeerById函数是合情合理的,这里使用一个回调函数把回应结果返回 现在我们将针对接口而不是实现进行编程
getBeerById(this.id,function (beer) {
console.log(`Request Beer:${res.responseText}`)
})
}
我们用了两种方式来实现,第一种方式把事件对象与getBeerById捆绑在一起,那么它只在本次事件中适用。第二种方式使用桥接模式把抽象隔离开来后,getBeerById 函数不再和事件对象捆绑在一起,也就扩大了它的适用范围
参照链接