JavaScript中常见的设计模式

文章目录

前言

一、单例模式

二、策略模式

三、代理模式

四、迭代器模式

五、发布-订阅模式(观察者模式)

六、命令模式

七、组合模式

八、模板方法模式

九、享元模式

十、职责链模式

十一、中介者模式

十二、装饰者模式

十三、状态模式

十四、适配器模式

总结


前言

借着这段时间拜读了曾探大神的《JavaScript设计模式与开发实践》一书。本文以此总结一下JS常见的设计模式与实现方法,主要做一些笔记以方便自己过后复习与加深理解,同时也希望把书中典型例子整理出来和大家分享,共同探讨和进步。


设计模式:在面向对象软件设计过程中针对特定问题的简洁而优雅地解决方案。

一、单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如:线程池、全局缓存、window对象等。

核心逻辑:一个变量来标志是否创建对象,如果是,返回该创建好的对象。

将创建对象和管理单例的职责分布在两个不同的方法中。

let obj;
if (obj) {
    obj = xxxxx;
}

例子:

// 核心逻辑
const getSingle = function( fn ) {
    let result;
    return function() {
        return result || ( result = fn.apply(this, arguments) );
    }
};

class Singletion {
    constructor(name) {
        this.name = name;
    }
    getName() {
        alert(this.name);
    }
}

const singletion = getSingle(function(name) {
    let sing = new Singletion(name);
    return sing;
});

singletion('a').getName();
singletion('b').getName();

二、策略模式

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

核心逻辑:将算法的使用与算法的实现分离开来。

一个基于策略模式的程序至少有两个部分组成。第一个部分是一组策略类,封装了具体的算法,

并负责具体的计算过程。第二个部分是环境类,接收客户的请求,随后把请求委托给某一个策略类。

例子一:

const strategies = {
    "S" : function(salary){
        return salary * 4;
    },
    "A" : function(salary){
        return salary * 3;
    },
    "B" : function(salary){
        return salary * 2;
    }
};

const calculateBonus = function(level, salary){
    return strategies[level](salary);
}

console.log(calculateBonus("S", 2000));

例子二:


  
    
请输入用户名:

 总结:

  • 利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 提供了对开放-封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于理解,易于扩展。
  • 其中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  • 利用组合和委托来让环境类拥有执行算法的能力,这也是继承的一种更方便的替代方案。

三、代理模式

定义:为一个对象提供一个代用品或占位符,以便控制对它的访问(如 保护代理、虚拟代理、缓存代理、防火墙代理、远程代理、写时复制代理等)

  1. 代理模式符合开放封闭原则
  2. 本体对象和代理对象拥有相同的方法,在用户看来并不知道请求的本体对象还是代理对象。

例子一(虚拟代理):

const miniConsole = (function() {
    const cache = [];
    const handler = function(e) {
        if(e.keyCode === 113) {
            const script = document.createElement('script');
            script.onload = function() {
                for(let i = 0, fn; fn = cache[i++];){
                    fn();
                }
            };
            script.src = 'miniConsole.js';
            document.getElementsByTagName('head')[0].appendChild(script);
            document.body.removeEventListener('keydown', handler);
        }
    };
    document.body.addEventListener('keydown', handler, false);
    return {
        log: function(){
            const args = arguments;
            cache.push(function() {
                return miniConsole.log.apply(miniConsole, args);
            });
        }
    }
})();

miniConsole.log(11);

例子二(缓存代理):

// 计算乘积
const mult = function() {
    let a = 1;
    for( let i = 0, l = arguments.length; i < l; i++){
        a = a * arguments[i];
    }
    return a;
};
// 计算加和
const plus = function() {
    let a = 1;
    for( let i = 0, l = arguments.length; i < l; i++){
        a = a + arguments[i];
    }
    return a;
}
// 创建缓存代理工厂
const createProxyFactory = function(fn) {
    const cache = {};
    return function() {
        const args = Array.prototype.join.call(arguments, ',');
        if(args in cache) {
            return cache[args];
        }
        return cache[args] = fn.apply(this, arguments);
    }
}

const proxyMult = createProxyFactory(mult),
proxyPlus = createProxyFactory(plus);

proxyMult(1, 2, 3);
proxyPlus(1, 2, 3);

四、迭代器模式

定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以顺利访问其中的每个元素。

// 简单的一个迭代器
class Iterator {
    constructor(obj) {
        this.current = 0;
        this.obj = obj;
    }
    next() {
        this.current += 1;
    }
    isDone() {
        return this.current >= this.obj.length;
    }
    getCurrItem() {
        return this.obj[this.current];
    }
}
const iterator = new Iterator([1,2,3,4]);
while (!iterator.isDone()) {
    console.log(iterator.getCurrItem())
    iterator.next()
}

例子:可以用于某些特殊结构迭代检查,如检测 js 某个功能在浏览器是否兼容性等

const getHuohu = function(){ 
    console.log('检测在火狐中是否可以执行');
    return false;
}
const getGuge = function(){
    console.log('检测在谷歌中是否可以执行');
    return true;
}
const iteratorObj = function(){
    for(let i = 0, fn; fn = arguments[i++];){
        let result = fn();
        if(result !== false){
            return result;
        }
    }
}
iteratorObj(getHuohu, getGuge)

五、发布-订阅模式(观察者模式)

定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

发布—订阅模式:

class Event{
    constructor(){
        this.clientList = {};
    }
    listen(key, fn){
        if(!this.clientList[key]){
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
    }
    trigger(){
        const key = Array.prototype.shift.call(arguments),
            fns = this.clientList[key];
        if(!fns || fns.length === 0){
            return false;
        }
        for(let i = 0, fn; fn = fns[i++];){
            fn.apply(this, arguments);
        }
    }
    remove(key, fn){
        const fns = this.clientList[key];
        if(!fns){
            return false;
        }
        if(!fn) {
            // 订阅类型的事件全部取消
            fns && (fns.length = 0);
        }else {
            for( let l = fns.length - 1; l >= 0; l--){
                let _fn = fns[l];
                if( _fn === fn){
                    fns.splice(l, 1);
                }
            }
        }
    }
}
let event = new Event();
event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
    console.log( '价格= ' + price ); // 输出:'价格=2000000' 
});
event.trigger( 'squareMeter88', 2000000 );

六、命令模式

定义:在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象实现二者之间的松耦合。使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

简单命令模式:

const closeDoorCommand = {
    execute: function() {
        // something
    }
};

例子:点击按钮之后,必须项某些负责具体行为的对象发送请求,这些对象就是请求的接收者。但是目前并不知道接收者是什么对象,也不知道接收者究竟会做什么。借助命令类的帮助,以便解开按钮和负责具体行为之间的耦合。




    

命令模式还支持撤销、重做、排队等操作。

七、组合模式

定义:在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象实现二者之间的松耦合。使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

JavaScript中常见的设计模式_第1张图片

例如一个万能遥控器里面添加一个命令的时候,并不关心这个命令是宏命令还是普通子命令。我们只需要确定它是一个命令,并且这个命令拥有可执行的execute方法,那么这个命令就可以被添加进万能遥控器。

class MacroCommand {
    constructor() {
        this.commandsList = [];
    }
    add(command) {
        this.commandsList.push(command);
    }
    execute(){
        for(let i = 0, command; command = this.commandsList[i++]; ) {
            command.execute();
        }
    }
}

class openAcCommand {
    execute() {
        console.log('打开空调');
    }
    add() {
        throw new Error('叶对象不能添加子节点');
    }
}

class openTvCommand {
    execute() {
        console.log('打开电视');
    }
    add() {
        throw new Error('叶对象不能添加子节点');
    }
}

const macroCommand = new MacroCommand();
macroCommand.add(new openAcCommand());
macroCommand.add(new openTvCommand());
macroCommand .execute();
// 打开空调
// 打开电视

 例子:扫描文件夹

class Folder{
    constructor(name) {
        this.name = name;
        this.parent = null;
        this.files = [];
    }
    add(file) {
        file.parent = this;
        this.files.push(file);
    }
    scan() {
        console.log('开始扫描文件夹:' + this.name);
        for( let i = 0, file, files = this.files; file = files[i++];){
            file.scan();
        }
    }
    remove() {
        if(!this.parent) {
            return;
        }
        for( let files = this.parent.files, l = files.length - 1; l >= 0; l--){
            const file = files[l];
            if(file == this) {
                files.splice(l, 1);
            }
        }
    }
}

class File {
    constructor(name) {
        this.name = name;
        this.parent = null;
    }
    add(file) {
        throw new Error('不能添加在文件下面');
    }
    scan() {
        console.log('开始扫描文件夹:' + this.name);
    }
    remove() {
        if(!this.parent) {
            return;
        }
        for( let files = this.parent.files, l = files.length - 1; l >= 0; l--){
            const file = files[l];
            if(file == this) {
                files.splice(l, 1);
            }
        }
    }
}

const folder = new Folder('学习资料');
const folder1 = new Folder('JavaScript');
const file1 = new File('node.js');
folder1.add( new File('宝可梦'));
folder.add(folder1);
folder.add(file1);
folder1.remove();
folder.scan();

八、模板方法模式

定义:是一种只需使用继承就可以实现的非常简单模式。由两部分结构组成,第一个部分是一个抽象父类,第二个部分是具体实现的子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

抽象类不能被实例化,一定是用来被某些具体类继承。父类规定了子类的方法和执行这些方法的顺序,子类就应该拥有这些方法,并且提供正确的实现。

class Beverage {
    // 模板方法
    init() {
        this.boilWater(); 
        this.brew(); 
        this.pourInCup();
        if ( this.customerWantsCondiments() ){ // 如果挂钩返回 true,则需要调料
            this.addCondiments(); 
        }
    }
    boilWater() {
       console.log( '把水煮沸' );
    }
    brew() {
       throw new Error( '子类必须重写 brew 方法' );
    }
    pourInCup() {
       throw new Error( '子类必须重写 pourInCup 方法' );
    }
    addCondiments() {
       throw new Error( '子类必须重写 addCondiments 方法' );
    }
    // 钩子
    customerWantsCondiments() {
        return true;
    }
}

class Coffee extends Beverage {
    brew() {
        console.log( '用沸水冲泡咖啡' );
    }
    pourInCup() {
        console.log( '把咖啡倒进杯子' );
    }
    addCondiments() {
        console.log( '加糖和牛奶' );
    }
    customerWantsCondiments() {
        return window.confirm( '请问需要调料吗?' );
    }
}

let coffee = new Coffee();
coffee.init();

好莱坞原则:允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件(子类放弃了对自己控制权,而是改为父类通知子类)。常用于其他模式和场景:例如发布-订阅模式和回调函数。

九、享元模式

定义:是一种用于性能优化的模式。运用共享技术来有效支持大量细粒度的对象。

将对象属性划分为内部状态与外部状态,尽量减少共有对象的数量。

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

以下情况发生时便可以使用享元模式:

  • 一个程序中使用了大量的相似对象
  • 由于使用了大量对象,造成很大的内存开销
  • 对象的大多数状态都可以变为外部状态
  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象

例子一(文件上传):



  
    
    
    
    Document
  
  
  

例子二(对象池):



  
    
    
    
    Document
  
  
  

十、职责链模式

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。

请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。

例子:

// 异步职责链
class Chain {
    constructor(fn) {
    this.fn = fn;
    // 下一个节点
    this.successor = null;
    }
    setNextSuccess(successor) {
    return (this.successor = successor);
    }
    passRequest() {
    const ret = this.fn.apply(this, arguments);
    if (ret === "nextSuccessor") {
        return (
        this.successor &&
        this.successor.passRequest.apply(this.successor, arguments)
        );
    }
    return ret;
    }
    // 异步,手动执行
    next() {
    return (
        this.successor &&
        this.successor.passRequest.apply(this.successor, arguments)
    );
    }
}
const f1 = new Chain(function () {
    console.log(1);
    return "nextSuccessor";
});
const f2 = new Chain(function () {
    console.log(2);
    setTimeout(() => {
    this.next();
    }, 3000);
});
const f3 = new Chain(function () {
    console.log(3);
});
f1.setNextSuccess(f2).setNextSuccess(f3);
f1.passRequest();

十一、中介者模式

定义:解除对象与对象之间的紧耦合关系。所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。

缺点是会增加一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者经常是巨大的。一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那么就可以考虑用中介者模式来重构代码。

例子(泡泡堂):

class Player {
    constructor(name, teamColor) {
    this.name = name;
    this.teamColor = teamColor;
    this.state = "alive";
    }
    win() {
    console.log(`${this.name} won`);
    }
    lose() {
    console.log(`${this.name} lost`);
    }
    die() {
    this.state = "dead";
    playerDirector.ReceiveMessage("playerDead", this);
    }
    remove() {
    this.state = "dead";
    playerDirector.ReceiveMessage("removePlayer", this);
    }
    changeTeam(color) {
    playerDirector.ReceiveMessage("changeTeam", this, color);
    }
}
const playerDirector = (function () {
    const players = {};
    const operations = {};
    operations.addPlayer = function (player) {
    const teamColor = player.teamColor;
    players[teamColor] = players[teamColor] || [];
    players[teamColor].push(player);
    };
    operations.removePlayer = function (player) {
    const teamColor = player.teamColor;
    const teamPlayers = players[teamColor] || [];
    for (let i = teamPlayers.length - 1; i >= 0; i--) {
        if (teamPlayers[i] === player) {
        teamPlayers.splice(i, 1);
        }
    }
    };
    operations.changeTeam = function (player, newTeamColor) {
    operations.removePlayer(player);
    player.teamColor = newTeamColor;
    operations.addPlayer(player);
    };
    operations.playerDead = function (player) {
    const teamColor = player.teamColor;
    const teamPlayers = players[teamColor];
    let all_dead = true;
    for (let i = 0, player; (player = teamPlayers[i++]); ) {
        if (player.state !== "dead") {
        all_dead = false;
        break;
        }
    }
    if (all_dead) {
        for (let i = 0, player; (player = teamPlayers[i++]); ) {
        player.lose();
        }
        for (let color in players) {
        if (color !== teamColor) {
            let teamPlayers = players[color];
            for (let i = 0, player; (player = teamPlayers[i++]); ) {
            player.win();
            }
        }
        }
    }
    };
    const ReceiveMessage = function () {
    const message = Array.prototype.shift.call(arguments);
    operations[message].apply(this, arguments);
    };
    return {
    ReceiveMessage,
    };
})();
const playerFactory = function (name, teamColor) {
    const newPlayer = new Player(name, teamColor);
    playerDirector.ReceiveMessage("addPlayer", newPlayer);
    return newPlayer;
};
const player1 = playerFactory("小白", "red");
const player2 = playerFactory("小红", "red");
const player3 = playerFactory("小明", "red");
const player4 = playerFactory("小花", "red");
const player5 = playerFactory("悟能", "blue");
const player6 = playerFactory("悟空", "blue");
const player7 = playerFactory("八戒", "blue");
const player8 = playerFactory("唐*", "blue");
player1.changeTeam("blue");
player1.die();
player2.die();
player3.die();
player4.die();

十二、装饰者模式

定义:在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。

例子:

Function.prototype.after = function (fn) {
  var _self = this;
  return function () {
    console.log(_self, this)
    var result = _self.apply(this, arguments);
    fn.apply(this, arguments);
    return result;
  };
};
Function.prototype.before = function (beforefn) {
  var __self = this;
  return function () {
    beforefn.apply(this, arguments);
    return __self.apply(this, arguments);
  }
}
document.getElementById = document.getElementById.before(function () {
  alert(1);
});
document.getElementById('button');

十三、状态模式

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。

例子一(开关):

class OffLightState {
    constructor(light) {
        this.light = light;
    }
    buttonWasPressed() {
        console.log('弱光');
        this.light.setState(this.light.weakLightState);
    }
}
class WeakLightState {
    constructor(light) {
        this.light = light;
    }
    buttonWasPressed() {
        console.log('强光');
        this.light.setState(this.light.offLightState);
    }
}
class Light {
    constructor() {
        this.offLightState = new OffLightState(this);
        this.weakLightState = new WeakLightState (this);
        this.currState = null;
    }
    init() {
        const self = this;
        this.currState = this.offLightState;
    }
    setState(newState) {
        this.currState = newState;
    }
    click() {
        this.currState.buttonWasPressed();
    }
}
const light = new Light();
light.init();
light.click();

十四、适配器模式

定义:又名包装器,作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

  • 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用
  • 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次
  • 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外观模式最显著的特点是定义了一个新的接口

例子:

const googleMap = {
    show: function() {
        console.log('开始渲染谷歌地图');
    }
}
const baiduMap = {
    display: function() {
        console.log('开始渲染百度地图');
    }
}
const baiduMpaAdapter = {
    show: function() {
        return baiduMap.display();
    }
}
const renderMap = function(map) {
    if(map.show instanceof Function) {
        map.show();
    }
}

renderMap(googleMap);
renderMap(baiduMpaAdapter)


总结

  • 单一职责原则(SRP):一个对象(方法)只做一件事情
  • 最少知识原则(LKP):一个软件实体应当尽可能少地与其他实体发生相互作用
  • 开放-封闭原则(OCP):软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改

你可能感兴趣的:(笔记,javascript,前端,原型模式,设计模式)