欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

必知!JavaScript中的13大常用设计模式解析

最编程 2024-08-06 12:51:25
...

介绍常用的Javascript设计模式。

常用设计模式分类

常用23 种设计模式可以分为三大类:

  • 创建型模式(Creational Patterns)
  • 结构型模式(Structural Patterns)
  • 行为型模式(Behavioral Patterns)

创建型模式(5种)

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

结构型模式(7种)

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  • 适配器模式(Adapter Pattern)
  • 装饰器模式(Decorator Pattern)
  • 桥接模式(Bridge Pattern)
  • 代理模式(Proxy Pattern)
  • 外观模式(Facade Pattern)
  • 组合模式(Composite Pattern)
  • 享元模式(Flyweight Pattern)

行为型模式(11种)

这些设计模式特别关注对象之间的通信。

  • 策略模式(Strategy Pattern)
  • 模版模式(Template Pattern)
  • 观察者模式(Observer Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 状态模式(State Pattern)
  • 职责链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 备忘录模式(Memento Pattern)
  • 解释器模式(Interpreter Pattern)
  • 访问者模式(Visitor Pattern)

23种设计模式分类图

单例模式(Singleton Pattern)

单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

实现

简单实现

/**
 * 单例模式
 */
class Singleton {
    constructor(name) {
        this.name = name;
        this.instance = null;
    }
    getName(){
        console.log(this.name);
    }
    static getInstance(name){
        if (!this.instance){
            this.instance = new Singleton(name);
        }
        return this.instance;
    };
}
const a = Singleton.getInstance('sven1');
const b = Singleton.getInstance('sven2');
console.log(a === b); // true

透明的单例模式

但是这个类需要实例化后调用方法才能创建实例,我们希望直接实例化时就能创建。

/**
 * 使用闭包实现单例模式
 */
const Singleton = (function(){
    let instance;
    class SingletonOrigin {
        constructor(name) {
            if (instance) {
                return instance;
            }
            this.name = name;
            instance = this;
            return this;
        }
        getName(){
            console.log(this.name);
        }
    }
    return SingletonOrigin;
})();

const a = new Singleton('sven1');
const b = new Singleton('sven2');
console.log(a === b); // true

为了把 instance 封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回 真正的 Singleton 构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。

使用代理实现单例模式

/**
 * 使用代理实现单例模式
 */
const Singleton = (function (){
    class SingletonOrigin {
        constructor(name) {
            this.name = name;
        }
        getName(){
            console.log(this.name);
        }
    }
    let instance;
    return function(name) {
        if (!instance){
            instance = new SingletonOrigin(name);
        }
        return instance;
    }
})();

const a = new Singleton('sven1');
const b = new Singleton('sven2');
console.log(a === b); // true

惰性单例模式

前面几种实现方式是基于面向对象思路的实现,现在使用js特殊的方式实现单例模式,名为惰性单例模式

惰性单例模式可以推迟创建对象的时机,并不在一开始就创建,所以叫惰性

/**
 * 惰性单例模式
 */
const getInstance = (function () {
    function createInstance(name) {
        return {
            name: name,
            getName() {
                console.log(this.name);
            }
        }
    }
    function getSingle (fn){
        let result;
        return function() {
            if (!result) {
                result = fn.apply(this, arguments)
            }
            return result;
        }
    };
    return getSingle(createInstance);
})();


const a = getInstance('sven1');
const b = getInstance('sven2');
console.log(a === b); // true

发布订阅模式(又叫观察者模式Observer Pattern)

实现发布订阅模式

介绍

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,一般用事件模型来替代传统的发布—订阅模式。

应用

  • dom的clickfocus等事件
  • 在Vue中的任意组件件通信事件总线EventBus
  • 含有订阅功能的系统中,如新闻app中订阅了报纸新闻;

源码

简单版

/**
 * 发布订阅模式
 */
class PublishSubscribePattern {
    constructor() {
        // 消息映射
        this.msgMap = {};
    }
    // 发布
    publish(name, param) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.forEach(subscribe => {
                subscribe.callback(param);
            });
        } else {
            console.log('无人订阅此消息:', name, param);
        }
    }
    // 订阅
    subscribe(name, callback) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.push({callback});
        } else {
            this.msgMap[name] = {
                name,
                subscribes: [{callback}]
            }
        }
    }
}
  • 使用
const event = new PublishSubscribePattern();
event.publish('news', 'this is news 1');
event.subscribe('news', (param) => {
    console.log('get news:', param);
});
event.publish('news', 'this is news 2');

带发布者版本

订阅者希望只订阅某一个发布者发布的消息

  • 源码
/**
 * 发布订阅模式
 */
class PublishSubscribePattern {
    constructor() {
        // 消息映射
        this.msgMap = {};
    }
    // 发布
    publish({name, param, publisher}) {
        const msg = this.msgMap[name];
        if (msg) {
            if (!publisher)  {
                throw new Error('未注册发布人:' + name);
            } else if (publisher === 'all') {
                msg.subscribes.forEach(e => e.callback(param));
            } else {
                let beAccept = false;
                msg.subscribes.forEach(e => {
                    if (e.publisher === publisher) {
                        beAccept = true;
                        e.callback(param);
                    }
                });
                if (!beAccept) {
                    console.log('无人订阅你的消息:', name, param);
                }
            }
        } else {
            console.log('无人订阅此消息:', name, param);
        }
    }
    // 订阅
    subscribe({name, publisher}, callback) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.push({
                publisher,
                callback
            });
        } else {
            this.msgMap[name] = {
                name,
                subscribes: [{
                    publisher,
                    callback
                }]
            }
        }
    }
}
  • 使用
const event = new PublishSubscribePattern();
event.publish({name: 'news', param: 'this is news 1', publisher: 'weather'});

event.subscribe({name: 'news', publisher: 'weather'}, (param) => {
    console.log(`get news from weather:`, param);
});

event.publish({name: 'news', param: 'this is news 2', publisher: 'weather'});
event.publish({name: 'news', param: 'this is news of newspaper', publisher: 'newspaper'});

/*
无人订阅此消息: news this is news 1
get news from weather: this is news 2
无人订阅你的消息: news this is news of newspaper
*/

复杂版

复杂版主要实现以下功能:

  • 取消订阅功能

  • 订阅者希望只订阅某一个发布者发布的消息

  • 订阅者希望获取到在订阅之前发布的历史消息

  • 订阅者希望查看自己订阅了哪些消息

  • 发布者希望查看自己发布过哪些消息

  • 代码

/**
 * 发布订阅模式
 */
class Event {
    constructor() {
        // 发布者映射
        this.publisherMap = {};
        // 订阅者映射
        this.subscriberMap = {};
    }
    // 注册
    registration(username, type = 'all') {
        if (type === 'publisher') {
            this.publisherMap[username] = {
                publisher: username,
                msgMap: {}
            };
            return this.publisherMap[username];
        } else if (type === 'subscriber') {
            this.subscriberMap[username] = {
                subscriber: username,
                msgMap: {}
            };
            return this.subscriberMap[username];
        } else if (type === 'all') {
            this.publisherMap[username] = {
                publisher: username,
                msgMap: {}
            };
            this.subscriberMap[username] = {
                subscriber: username,
                msgMap: {}
            };
        }
    }
    // 发布
    publish({name, param, publisher}) {
        const publisherObj = this.publisherMap[publisher];
        if (!publisherObj)  {
            throw new Error('未注册发布人:' + name);
        } else {
            const historyRecord = {
                name,
                param,
                publisher,
                time: Date.now()
            };
            const msg = publisherObj.msgMap[name];
            if (msg) {
                let beAccept = false;
                msg.subscribes.forEach(e => {
                    if (e.publisher === publisher) {
                        beAccept = true;
                        e.callback(param, {name, param, publisher});
                        console.log(e.subscriber, '收到了', e.publisher, '发布的消息', name, param);
                    }
                });
                if (!beAccept) {
                    console.log('无人订阅你的消息:', name, param);
                }
                msg.history.push(historyRecord);
            } else {
                publisherObj.msgMap[name] = {
                    name,
                    publisher: publisher,
                    subscribes: [],
                    history: [historyRecord]
                };
                console.log('发布者', publisher, '注册消息:', name, param);
            }
        }
    }
    // 订阅
    subscribe({name, publisher, subscriber, receiveHistoryMsg}, callback) {
        const publisherObj = this.publisherMap[publisher];
        if (subscriber) {
            const subscriberObj = this.subscriberMap[subscriber];
            if (subscriberObj) {
                subscriberObj.msgMap[name] = {
                    name,
                    publisher,
                    subscriber: subscriber,
                    callback,
                    time: Date.now()
                };
            }
        }
        if (publisherObj) {
            const msg = publisherObj.msgMap[name];
            if (msg) {
                msg.subscribes.push({
                    publisher,
                    subscriber,
                    callback
                });
                console.log(subscriber || '游客', '订阅了', publisher, '的消息:', name);
                if (receiveHistoryMsg === true) {
                    msg.history.forEach(e => callback(e.param, e)); 
                }
            } else {
                console.log('发布者', publisher, '未注册过此消息:', name);
            }
        } else {
            console.log('发布者未注册:', publisher);
        }
    }
    // 取消订阅
    unsubscribe({name, publisher, subscriber}) {
        const publisherObj = this.publisherMap[publisher];
        if (subscriber) {
            const subscriberObj = this.subscriberMap[subscriber];
            if (subscriberObj) {
                delete subscriberObj.msgMap[name];
            }
        }
        if (publisherObj) {
            const msg = publisherObj.msgMap[name];
            if (msg) {
                msg.subscribes = msg.subscribes.filter(e => !(e.publisher === publisher && msg.name === name));
            } else {
                console.log('发布者', publisher, '未注册过此消息:', name);
            }
        } else {
            console.log('发布者未注册:', publisher);
        }
    }
    // 获取发布历史消息
    getPublishHistory(publisher, name) {
        return this.publisherMap[publisher].msgMap[name].history;
    }
    getSubscribeMsg(subscriber) {
        return this.subscriberMap[subscriber].msgMap;
    }
}

  • 使用
// 直接使用Event实现发布订阅功能
const event = new Event();

const publisher = 'A';

const subscriber = 'B';

event.registration(publisher);
event.registration(subscriber);

const name = 'news';

const param = '一条消息a';

event.publish({name, publisher, param});

event.subscribe({name, publisher, subscriber, receiveHistoryMsg: true}, (param, e) => {
    console.log(`---- 接收消息from:`, param, e);
});

event.publish({name, publisher, param: '一条消息b'});

console.log('订阅的消息', event.getSubscribeMsg(subscriber));

event.unsubscribe({name, publisher, subscriber});

event.publish({name, publisher, param: '一条消息c'});

console.log('发布历史', event.getPublishHistory(publisher, name));

/*
发布者 A 注册消息: news 一条消息a
B 订阅了 A 的消息: news
---- 接收消息from: 一条消息a { name: 'news',
  param: '一条消息a',
  publisher: 'A',
  time: 1603011782573 }
---- 接收消息from: 一条消息b { name: 'news', param: '一条消息b', publisher: 'A' }
B 收到了 A 发布的消息 news 一条消息b
订阅的消息 { news:
   { name: 'news',
     publisher: 'A',
     subscriber: 'B',
     callback: [Function],
     time: 1603011782575 } }
无人订阅你的消息: news 一条消息c
发布历史 [ { name: 'news',
    param: '一条消息a',
    publisher: 'A',
    time: 1603011782573 },
  { name: 'news',
    param: '一条消息b',
    publisher: 'A',
    time: 1603011782577 },
  { name: 'news',
    param: '一条消息c',
    publisher: 'A',
    time: 1603011782578 } ]
*/

使用适配器类

  • 代码
/**
 * 代理类,屏蔽重复设置发布者、订阅者
 */
class Factory {
    constructor(username, type) {
        this.username = username;
        this.type = type;
        this._event = new Event();
        this._event.registration(username, type || 'all');
    }
    // 发布
    publish(param) {
        return this._event.publish(Object.assign({}, param, {publisher: this.username}))
    }
    // 订阅
    subscribe(param, callback) {
        return this._event.subscribe(Object.assign({}, param, {subscriber: this.username}), callback);
    }
    // 取消订阅
    unsubscribe(param) {
        return this._event.unsubscribe(Object.assign({}, param, {subscriber: this.username}));
    }
    // 获取历史发布消息
    getPublishHistory(name) {
        return this._event.getPublishHistory(this.username, name);
    }
    // 获取订阅的消息列表
    getSubscribeMsg() {
        return this._event.getSubscribeMsg(this.username);
    }

}

  • 使用
// 使用适配器封装
const publisherA = 'A';
const subscriberB = 'B';
const publisher = new Factory(publisherA, 'publisher');
const subscriber = new Factory(subscriberB, 'subscriber');

const name = '新闻';

publisher.publish({name, param: 'this is news 1'});

subscriber.subscribe({name, publisher: publisherA, receiveHistoryMsg: true}, (param) => {
    console.log(`---- get news from ${publisherA}:`, param);
});
console.log('订阅的消息', subscriber.getSubscribeMsg());

publisher.publish({name, param: 'this is news 2'});

publisher.publish({name, param: 'this is news of newspaper'});

console.log('发布历史', publisher.getPublishHistory(name));

/*
发布者 A 注册消息: 新闻 this is news 1
发布者未注册: A
订阅的消息 { '新闻':
   { name: '新闻',
     publisher: 'A',
     subscriber: 'B',
     callback: [Function],
     time: 1603012329816 } }
无人订阅你的消息: 新闻 this is news 2
无人订阅你的消息: 新闻 this is news of newspaper
发布历史 [ { name: '新闻',
    param: 'this is news 1',
    publisher: 'A',
    time: 1603012329813 },
  { name: '新闻',
    param: 'this is news 2',
    publisher: 'A',
    time: 1603012329819 },
  { name: '新闻',
    param: 'this is news of newspaper',
    publisher: 'A',
    time: 1603012329819 } ]
*/

中介者模式(Mediator Pattern)

中介者模式的作用就是解除对象与对象之间的紧耦合关系。

介绍

增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。

中介者模式是迎合最少知识原则(迪米特法则)的一种实现。是指一个对象应 该尽可能少地了解另外的对象(类似不和陌生人说话)。

img

举例说明

用一个小游戏说明中介者模式的用处。

**游戏规则:**两组选手进行对战,其中一个玩家死亡的时候游戏便结束, 同时通知它的对手胜利。

普通实现

var players = [];
//接着我们再来编写Hero这个函数;代码如下:

var players = []; // 定义一个数组 保存所有的玩家
function Hero(name,teamColor) {
    this.friends = [];    //保存队友列表
    this.enemies = [];    // 保存敌人列表
    this.state = 'live';  // 玩家状态
    this.name = name;     // 角色名字
    this.teamColor = teamColor; // 队伍的颜色
}
Hero.prototype.win = function(){
    console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
    console.log("lose:" + this.name);
};
Hero.prototype.die = function(){
    // 所有队友死亡情况 默认都是活着的
    var all_dead = true;
    this.state = 'dead'; // 设置玩家状态为死亡
    for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) {
        // 遍历,如果还有一个队友没有死亡的话,则游戏还未结束
        if(this.friends[i].state !== 'dead') {
            all_dead = false; 
            break;
        }
    }
    if(all_dead) {
        this.lose();  // 队友全部死亡,游戏结束
        // 循环 通知所有的玩家 游戏失败
        for(var j = 0,jlen = this.friends.length; j < jlen; j+=1) {
            this.friends[j].lose();
        }
        // 通知所有敌人游戏胜利
        for(var j = 0,jlen = this.enemies.length; j < jlen; j+=1) {
            this.enemies[j].win();
        }
    }
}
// 定义一个工厂类来创建玩家 
var heroFactory = function(name,teamColor) {
    var newPlayer = new Hero(name,teamColor);
    for(var i = 0,ilen = players.length; i < ilen; i+=1) {
        // 如果是同一队的玩家
        if(players[i].teamColor === newPlayer.teamColor) {
            // 相互添加队友列表
            players[i].friends.push(newPlayer);
            newPlayer.friends.push(players[i]);
        }else {
            // 相互添加到敌人列表
            players[i].enemies.push(newPlayer);
            newPlayer.enemies.push(players[i]);
        }
    }
    players.push(newPlayer);
    return newPlayer;
};
        // 红队
var p1 = heroFactory("aa",'red'),
    p2 = heroFactory("bb",'red'),
    p3 = heroFactory("cc",'red'),
    p4 = heroFactory("dd",'red');
        
// 蓝队
var p5 = heroFactory("ee",'blue'),
    p6 = heroFactory("ff",'blue'),
    p7 = heroFactory("gg",'blue'),
    p8 = heroFactory("hh",'blue');
// 让红队玩家全部死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:dd lose:aa lose:bb lose:cc
// win:ee win:ff win:gg win:hh

中介者模式实现

玩家与玩家之间的耦合代码解除,把所有的逻辑操作放在中介者对象里面进去处理,某个玩家的任何操作不需要去遍历去通知其他玩家,而只是需要给中介者发送一个消息即可,中介者接受到该消息后进行处理,处理完消息之后会把处理结果反馈给其他的玩家对象。

var players = []; // 定义一个数组 保存所有的玩家
function Hero(name,teamColor) {
    this.state = 'live';  // 玩家状态
    this.name = name;     // 角色名字
    this.teamColor = teamColor; // 队伍的颜色
}
Hero.prototype.win = function(){
    // 赢了
    console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
    // 输了
    console.log("lose:" + this.name);
};
// 死亡
Hero.prototype.die = function(){
    this.state = 'dead';
    // 给中介者发送消息,玩家死亡
    playerDirector.ReceiveMessage('playerDead',this);
}
// 移除玩家
Hero.prototype.remove = function(){
    // 给中介者发送一个消息,移除一个玩家
    playerDirector.ReceiveMessage('removePlayer',this);
};
// 玩家换队
Hero.prototype.changeTeam = function(color) {
    // 给中介者发送一个消息,玩家换队
    playerDirector.ReceiveMessage('changeTeam',this,color);
};
// 定义一个工厂类来创建玩家 
var heroFactory = function(name,teamColor) {
    // 创建一个新的玩家对象
    var newHero = new Hero(name,teamColor);
    // 给中介者发送消息,新增玩家
    playerDirector.ReceiveMessage('addPlayer',newHero);
    return newHero;
};
var playerDirector = (function(){
    var players = {},  // 保存所有的玩家
        operations = {}; // 中介者可以执行的操作
    // 新增一个玩家操作
    operations.addPlayer = function(player) {
        // 获取玩家队友的颜色
        var teamColor = player.teamColor;
        // 如果该颜色的玩家还没有队伍的话,则新成立一个队伍
        players[teamColor] = players[teamColor] || [];
        // 添加玩家进队伍
        players[teamColor].push(player);
     };
    // 移除一个玩家
    operations.removePlayer = function(player){
        // 获取队伍的颜色
        var teamColor = player.teamColor,
        // 获取该队伍的所有成员
        teamPlayers = players[teamColor] || [];
        // 遍历
        for(var 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) {
    var teamColor = player.teamColor,
    // 玩家所在的队伍
    teamPlayers = players[teamColor];

    var all_dead = true;
    //遍历 
    for(var i = 0,player; player = teamPlayers[i++]; ) {
        if(player.state !== 'dead') {
            all_dead = false;
            break;
        }
    }
    // 如果all_dead 为true的话 说明全部死亡
    if(all_dead) {
        for(var i = 0, player; player = teamPlayers[i++]; ) {
            // 本队所有玩家lose
            player.lose();
        }
        for(var color in players) {
            if(color !== teamColor) {
                // 说明这是另外一组队伍
                // 获取该队伍的玩家
                var teamPlayers = players[color];
                for(var i = 0,player; player = teamPlayers[i++]; ) {
                    player.win(); // 遍历通知其他玩家win了
                }
            }
        }
    }
};
var ReceiveMessage = function(){
    // arguments的第一个参数为消息名称 获取第一个参数
    var message = Array.prototype.shift.call(arguments);
    operations[message].apply(this,arguments);
};
return {
    ReceiveMessage : ReceiveMessage
};
})();
// 红队
var p1 = heroFactory("aa",'red'),
    p2 = heroFactory("bb",'red'),
    p3 = heroFactory("cc",'red'),
        p4 = heroFactory("dd",'red');
        
    // 蓝队
    var p5 = heroFactory("ee",'blue'),
        p6 = heroFactory("ff",'blue'),
        p7 = heroFactory("gg",'blue'),
        p8 = heroFactory("hh",'blue');
    // 让红队玩家全部死亡
    p1.die();
    p2.die();
    p3.die();
    p4.die();
    // lose:aa lose:bb lose:cc lose:dd 
   // win:ee win:ff win:gg win:hh

策略模式(Strategy Pattern)<