js之设计模式

一,概念

设计模式是针对面向对象开发的一种问题解决方案。目的是以后开发的过程中,面对某些业务场景,可以顺利采用合适的设计模式,使代码更低耦合,易维护。

常见的设计模式有单例模式、工厂模式、策略模式、代理模式、中介者模式、装饰者模式等。

二,单例模式

原理

单例模式是指保证一个类只有一个实例,且提供一个全局的访问点,在访问他的实例时,先判断当前实例是否已存在一个,如果存在则返回,如果不存在则先创建再返回。

使用场景

很多场景都可以使用单例模式,例如模态框,这种一个页面只能同时存在一个的,也不需要多次创建的,无论点击多少次,模态框都只创建一次;封装websocket的链接池(类似于数据库连接池)

示例

// 实现的方式有几种,可以使用原型、闭包、类+构造函数实现

// 定义类
class Person() {
    // 构造函数
    countructor(id, name) {
        this.id = id; // 设置属性
        this.name = name;
    }
    // 设置方法
    getName() {
        console.log(this.id, this.name);
    }
}

// 使用var声明一个闭包,用于创建实例,这种也叫“代理实现单例模式”
var Instanse = (function () {
    var instanse = null;
    return function (id, name) {
        if (!instanse) {
            instanse = new Person(id, name);
        }
        return instanse;
    }
})();

// 调用
var persopn = new Instanse('ins-1', '张三');

三,工厂模式

原理

工厂模式是创建对象最常用的方式之一,主要核心是把创建各种对象的代码逻辑封装起来,外部调用时只传入参数即可,不必一个个实例化他们,不用关心内部逻辑。减少代码重复,也保证了代码的低耦合,降低开发者重复编写创建流程的代码错误等。

根据业务场景可以细分为普通工厂模式和抽象工厂模式。

普通工厂模式:工厂方法会根据传入参数的不同,生成对应的实例并返回

                        (1)静态工厂模式:较简单的,构造函数通过参数判断并return实例

                        (2)工厂方法模式:在静态工厂的基础下,把具体的实例创建封装到prototype中

抽象工厂模式:多一个抽象类(类似java中的抽象类),理解为工厂方法内部生成的是一个工厂

使用场景

UI库创建组件;创建数据模型等。

他是最常用的创建对象方式。如果需要new一个实例(有构造函数的地方),就可以考虑使用厂模式;遇到几个实例有类似的部分,也可以使用工厂模式。大型项目的类似场景,可以使用抽象工厂模式,通常日常开发都是使用普通工厂模式。

示例

静态工厂模式:

// 定义工厂
function Factory(type) {
    // 定义创建构实例的造函数
    function Person(type, style) {
        this.type = type;
        this.style = style;
    }

    let style;
    switch (type) { // 根据类型生成不同的实例
        case 'man':
            style = {fat: true, beautiful: false};
            return new Person('man', style);
            break;
        case 'woman':
            style = {fat: false, beautiful: true};
            return new Person('woman', style);
            break;
        case 'child':
            style = {fat: false, beautiful: true};
            return new Person('child', style);
            break;
    }
}

// 调用
let person = new Factory('man');

工厂方法模式:

// 定义工厂
function Factory(type) {
    if(this instanceof Factory){
        var f = new this[type](); // 实例化具体类型的对象
        return f;
    }else{
        return new Factory(type);
    }
}
// 在工厂方法中定义所有类型对象的构造函数
Factory.prototype = {
    'man': function() {
        this.type = 'man';
        this.style = {fat: true, beautiful: false};
    },
    'man': function() {
        this.type = 'woman';
        this.style = {fat: false, beautiful: true};
    },
    'child': function() {
        this.type = 'child';
        this.style = {fat: false, beautiful: true};
    }
}

// 调用
let person = new Factory('man');

抽象工厂模式:

因js没有抽象类的概念,因此只能通过代码模拟实现。具体的实现流程如下:

1,抽象工厂类

2,抽象类

3,具体类

4,实例化具体类

四,策略模式

原理

策略模式就是将一系列的算法或者逻辑一个个的封装起来,将代码逻辑中不变的和变化的部分分开编码。不变的部分叫做策略类(封装了具体的算法和执行过程);变化的部分叫做环境类(用于分配使用哪个策略算法)。

应用场景

特别常用的业务场景!通常是有多种状态或多种情况,对于代码的封装。例如用js原生实现需要很多if/else的场景不同环境使用不同参数的场景等等

示例

// 封装策略类,代替多个if else(每种情况就是封装的一个策略算法)
var moneyObj = {
    "vvip": function(score) { // vvip,1积分 = 1块钱
        return score / 1;
    },
    "vip": function(score) {
        return score / 10; // vip,10积分 = 1块钱
    },
    "normal": function(score) { // 普通用户
        return score / 100; // vip,100积分 = 1块钱
    },
}

// 定义环境类,用于分配具体调用哪种策略
function getVipMoney(level, score) {
    return moneyObj[level](score);
}

// 调用,计算当前会员卡积分可兑换多少钱
var leftMoney = getVipMoney('vip', 5300);

五,代理模式

原理

顾名思义就是为一个对象提供代用品或者占位符,来控制对他的访问。最简单的实现就是ES6的Proxy对象,轻松实现了代理。代理模式的意义是用户不方便直接访问对象,或者不满足直接访问对象,就通过访问一个替身对象,而让替身对象去访问实际对象

应用场景

应用比较广泛,常见的前端框架例如axios请求拦截器缓存懒加载权限控制等。

        缓存代理:为一个开销很大的运算进行提供暂时的存储,如果下次调用还是同样的参数,则直接返回。

        虚拟代理:为一个开销很大的运算进行延迟加载,等用到这个对象的时候再去创建。

示例

缓存代理:

// 实际对象
function add() {
    let result;
    for (let i = 0; i < arguments.length; i++) {
        result += arguments[i];
    }
    return result;
}

// 代理对象(替身对象)
var proxyAdd = (function() {
    var cache = {};
    return function() {
        var args = arguments.join(',');
        if (args in cache) {
            return cache[args]; // 从缓存获取
        }
        // 调用实际对象获取,计算的值存到缓存再返回
        return (cache[args] = add.apply(this, arguments));
    }
})();

// 调用
var result = proxyAdd(2, 3, 5);

虚拟代理:

// 实际对象,只暴露一个对外的setSrc接口
let img = (function(){
    let node = document.createElement('img');
    document.body.appendChild(imgNode);

    return {
        // 设置图片的路径
        setSrc: function(src){
            node.src = src;
        }
    }
})();

// 代理对象(替身对象),负责图片预加载功能
let proxyImg = (function(){
    let img = new Image;

    // 监听到图片加载完成后,再设置图片的url
    img.onload = function(){
        img.setSrc(this.src);
    }

    return {
        // 图片未被真正加载好时,显示loading图
        setSrc: function(src){
            myImage.setSrc('./assect/loading.gif');
            img.src = src;
        }
    }
})();

// 调用
proxyImg.setSrc('http://xxxx.jpg');

六,观察者模式

原理

观察者模式就是观察者和被观察者之间的通信。定义了对象一对多的关系,当一个对象发生改变时,所有依赖他的对象都会收到通知,并进行相应的更新操作。

应用场景

通常用于页面的事件监听器;回调数据变化时的更新和通知;发布订阅模型的实现等

示例

// 观察者构造函数
function Observer(name) {
    this.name = name;
}
// 观察者更新数据
Observer.Prototype.update = function(data) {
    console.log('更新观察者' + this.name, data);
}

// 被观察者构造函数
function Subject() {
    this.observers = []; // 观察者列表
}
// 添加观察者
Subject.prototype.add = function(observer) {
    if (this.observers.indexOf(observer) === -1) {
        this.observers.push(observer);
    }
}
// 删除观察者
Subject.prototype.delete = function(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
        this.observers.splice(index, 1);
    }
}
// 通知观察者
Subject.prototype.notice = function (data) {
  this.observers.forEach(function (observer) {
    observer.update(data);
  });
}

// 调用
var sub = new Subject(); // 创建被观察者
var ob1 = new Observer('张三'); // 创建观察者
var ob2 = new Observer('李四'); // 创建观察者

sub.add(ob1); // 添加观察者,关联关系
sub.add(ob2); // 添加观察者,关联关系
sub.notice('更新名称'); // 通知观察者

七,发布-订阅模式

原理

发布订阅模式与观察者模式类似,但是这里的发布者和订阅者没有依赖关系。发布订阅模式主要核心是发布者将消息分为不同的类别,发布到一个订阅中心订阅者可以根据需求去订阅一个或多个类别

与观察者模式的区别

  • 观察者模式中二者有依赖关系,被观察者发生变化通知观察者后,观察者进行更新;而发布订阅模式中二者没有依赖关系,订阅中心相当于代理方
  • 观察者模式通知消息属于同步通知(被观察者发送通知即观察者立即更新);而发布订阅模式通知属于异步通知,使用消息队列
  • 观察者模式二者之间是紧耦合;而发布订阅模式二者之间是松耦合

因此项目中根据不同的业务场景来确定需要使用的设计模式。

应用场景

也可以用于页面事件的监听和处理;用于消息之间通信和传递;

示例

// 订阅者构造函数
function Subscriber(name) {
    this.name = name;
}
// 订阅者接收消息
Subscriber.prototype.reciver = function(data) {
    console.log('订阅者' + this.name + '更新消息', data);
}

// 发布者构造函数
function Publisher() {
    this.listenerMsgs = {}; // 订阅者消息订阅中心
}
// 订阅者订阅一个消息主题
Publisher.prototype.subscribe = function (topic, subscriber) {
    if (!this.listenerMsgs[topic]) {
        this.listenerMsgs[topic] = [];
    }
    this.listenerMsgs[topic].push(subscriber);
}
// 订阅者取消订阅一个消息主题
Publisher.prototype.unsubscribe = function (topic, subscriber) {
    if (this.listenerMsgs[topic]) {
        const index = this.listenerMsgs[topic].indexOf(subscriber);
        if (index !== -1) {
            this.listenerMsgs[topic].splice(index, 1);
        }
    }
}
// 发布消息
Publisher.prototype.publish = function(topic, data) {
    this.listenerMsgs[topic].forEach((subscriber) => {
        subscriber.reciver(data); // 调用订阅者接收消息
    });
}

// 调用
var pub = new Publisher(); // 创建发布者
var sub1 = new Subscriber('张三'); // 创建订阅者1
var sub2 = new Subscriber('李四'); // 创建观察者2

pub.subscribe('新闻', sub1); // 订阅者1订阅了新闻主题
pub.subscribe('娱乐', sub1); // 订阅者1订阅了娱乐主题
pub.subscribe('娱乐', sub2); // 订阅者2订阅了娱乐主题

pub.publish('娱乐', '菜bd塌房了'); // 发布者发布消息
pub.publish('新闻', '俄乌最新进展'); // 发布者发布消息

八,装饰者模式

原理:

在不改变原始对象的前提下,在程序运行期间给对象动态添加方法,来满足现有的需求

应用场景:

即原理所述,在不修改原始对象的情况下,给其添加额外的功能;在运行时动态添加新的方法。

具体的业务场景比如需求迭代更新,原有的一系列方法需要修改时,在满足开放封闭原则(对外开放,对内修改封闭)的前提下,就可以使用此模式。(传统方式可能是修改原有核心逻辑,因此装饰者模式主要应对此场景)

示例

// 原始的打印功能
function Print() {
    this.log = function() {
        console.log('A4大小灰色打印');
    }
}

// 新需求-修改大小打印(定义新的装饰类)
function NewSizePrint(print) {
    this.print = print;
    this.log = function() {
        this.print.log(); // 原始的打印功能
        // 新打印功能
        console.log('新大小');
    }
}

// 调用
var print = new Print('xxx');
var newPrint = new NewSizePrint(print);
newPrint.print();

九,适配器模式

原理

将老的接口转换成另外的一个接口,在适配器中实现对老接口的兼容,也可以称为转换器

代理模式、装饰者模式、适配器模式的区别

代理模式,主要是隔离,通过第三方去实现

装饰者模式,主要是扩展,对于现有功能的扩展实现

适配器模式,主要是兼容,对于各兼容场景封装一个转换器

应用场景

第三方库等一些兼容性写法的场景;兼容不同浏览器的不同语法写法、jquery中的兼容性写法等。

示例

// jquery的$('selector').on 的实现
function on(target, event, callback) {
    if (target.addEventListener) { // 标准事件监听
        target.addEventListener(event, callback);
    } else if (target.attachEvent) { // IE低版本事件监听
        target.attachEvent(event, callback)
    } else { // 低版本浏览器事件监听
        target[`on${event}`] = callback
    }
}

十,迭代器模式

原理

就是封装一个迭代器,为不同数据结构的集合提供统一的迭代接口,且不暴露内部结构

应用场景

如原理所述,封装一个不暴露内部结构的迭代器。

示例

// 定义迭代器接口
function Iterator(list) {
    this.list = list;
    this.index = 0;

    this.hasNext = function() {
        if (this.index < this.list.length) {
            return true;
        }
        return false;
    }

    this.next = function() {
        if (this.hasNext()) {
            return this.list[this.index++];
        }
        return null;
    }
}

// 主体
function Container(list) {
    // 生成遍历器
    this.getIterator = function() {
        return new Iterator(list);
    }
}

// 调用
const arr = [1, 2, 3, 4];
const container = new Container(arr);
const iterator = container.getIterator();
while (iterator.hasNext()) {
    console.info(iterator.next());
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妍思码匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值