为了账号安全,请及时绑定邮箱和手机立即绑定

白话设计模式——中

标签:
设计模式

  在上篇说到了几个最常用的设计模式比如工厂,比如策略,还有观察者,中篇来了解四个模式。
  4、单例模式
  5、装饰者模式
  6、适配器模式
  7、享元模式

单例模式

  单例,顾名思义,它有且仅有一个的实例对象,或者说简单的说,它是一个全局的变量。对于类似Java等有类这种概念的语言而言,单例的实现通常是一个类,这个类是实例的引用,而单例有且仅有一个这样的特点,使得单例通常的实现是这样子的,判断环境中是否存在这个实例,没有则创建,若有则仅仅做一下返回,这和我们经常遇到的new出来的实例有一些不同。
  简单联系下应用,平时在写一些页面或Web应用,很多时候需要做一个全局的缓存,为了避免全局变量过多,通常会在全局的环境下创建一个变量,比如叫做options,用来存储各类的信息,同时也可以更好协调和管理系统整体的行为,最常遇到的应用比如说Vue中用于管理组件状态,作为数据交换中心的Vuex,这样一个看似简单的全局变量,其实就是一个单例
  单例的实现可以有这几重境界:
  第一重境界:var一个全局的变量,用完被GC回收,初始化又重新创建
  第二重境界:构建一个构造函数或一个伪类,通过new实例化一个对象出来,只不过new之前先判断环境中是否已存在,有则不再new
  第三重境界:可能应用于后端的多线程访问时的内存管理,为线程上锁,减少因瞬间的高并发而产生局部时刻的内存负载过高。
  笔者现在处于第二重境界,能new一个实例这样吧 = =。
  我们看看代码吧:


//一个构造函数,new出来的。
export class Person {

    constructor(name, age) {
        
        this.name = name;
        this.age = age;

        this.init();
    }

    init() {

        this.personId = this.createPersonId();
    }

    createPersonId() {
        
        return new Date().getTime().toString(32) + Math.floor((Math.random() * 999999)).toString(32);
    }

}

// 单例模式
export let singlePattern = (function () {
    
    let res;

    function init() {
        
        let privateProp = "这里是私有属性,可常规值,也可函数";

        return {

            publicOptions: {

                index: 8,
                method() {
                    console.log('公有方法');
                }

            }
        }
    }

    return 'undefined' !== typeof res ? res : init();
})();

看一下调用:

import { Person, singlePattern} from "./白话设计模式——中.js";

let person0 = new Person('小明', 24);

let person1 = new Person('小明', 24);

console.log(person0 === person1);


let single0 = singlePattern;

let single1 = singlePattern;

console.log(single0 === single1);

console.log(single0);

  可以看到两个person是不等的,而两个single则是完全相等的。这就是单例模式下实例唯一性的特点

装饰者模式

  装饰者模式,人如其名,装饰,简单的说,在原有小白纸般仅有一些基础内容的主体上增加各种各样的装饰,不同的装饰有不同的价格,或者说输出,而这就是装饰者模式。
  装饰者模式应用其实也非常的广泛,举个非常简单的例子,一般公司所做的软件项目或产品,都有一个主线,这条主线或者说基线承载着公司整个产品规划,未来战略布局的发展方向,但主线毕竟是主线,一些定制化的需求,一些个性化的效果,难以合并进基线,也由此会衍化出各条分支,而分支都是在主线的基础上添加各种各样的定制,或者说,装饰,而这其实就是一个很常见的装饰者模式。
  装饰者最大的好处就是无需改变原有基类的代码,仅仅是动态的附加各种功能,并通过各种各样的配置,衍生出无穷无尽的各个定制化对象。当然也可以应对花样繁多的各种可能出现的状况。
  比如:点外卖
  我们点外卖的时候,主体是一份主食,比如一份脆皮鸡饭,但是顾客除了点脆皮鸡之外,通常可能还会点一些其他的比如加根火腿肠,或者说加个鸡腿,或者说加瓶饮料,还有…而作为开发软件的工程师而言,他根本不可能将这些可能的排列组合都去搞出来,那样的if/else你可以想象有多长,当然现实肯定不会这样做,最常见的做法就是,将这些火腿肠,鸡腿,饮料都变成一个个的装饰,并附带上各自的价格,等到消费者选择到某个装饰的时候,再来往主体,也就是脆皮鸡的基础上添加额外的价格,而这,也是一个装饰者模式。
  一起来看一下代码吧:

// 装饰者模式
export class waiMai {

    
    constructor(arr) {
        
        this.cost = this.decorateCost(arr) + this.stapleFood();
    }

    stapleFood() {
        return 15;
    }
    sausage() {
        return 3;
    }
    drumstick() {
        return 6;
    }
    drinks() {
        return 2;
    }

    decorateCost(arr) {
        let cost = 0;
        arr.forEach(item => cost += this[item]());
        return cost;
    }

}

最终的调用则是:

//  当然,这里输入的数组里的每一项,也许就是你每一次的按钮点击。
let dorseyWaiMai = new waiMai(['sausage', 'drinks']);

console.log(`我买了脆皮鸡,另外加了香肠和饮料,我应该付款 ${ dorseyWaiMai.cost } 元`);

适配器模式

  适配器模式,什么是适配器?顾名思义,适配,比如多终端适配,多浏览器适配,它其实有另一个别称,叫做兼容。
  兼容相信对于每一位前端来说,都不陌生,而这个适配器模式是不是只应用于前端呢?不是的,更准确的说,设计模式的应用主要还是后端,那后端这块的应用有什么?
  适配器模式在后端其实更加常见,因为后端掌握的是数据,而数据信息时代的今天,这才是根本。而数据本身其实非常的混乱,不同数据源间可能不止字段不一致,甚至连数据结构都不一致,而这时候需要做的是将这些来自不同地方的数据源格式化输出成统一的格式,比如写一个网关……而这种数据结构转化的过程中,就是一种适配,对接除了前后端联调,其实也有很多是后端间的对接,那后端之间的对接是对接什么?就是对接这样的数据,你简单写的一个数据结构的转化,就是一种适配,你一直在写适配器模式的代码,只是你可能未曾察觉。
  当然,适配器的应用不单单只是存纯粹的数据,也可以是类,也可以是对象,还可以是接口,比如说,你公司对外的某第三方接口的格式是这样的,而你客户希望是那样的,当你的客户是强势方时,你可能还需要重新为其开发一个定制化的接口,而这也可以看做是一种适配
  一起来看一段适配器模式的代码吧。
  数据结构的统一格式化输出我们就不写了,写一个简单的功能适配吧,让一个原本不具备某一功能的对象,通过一些适配,一些狸猫换太子,让其具备该功能。
  比如我们让数组拥有个Math对象一样的比较大小的功能。

//  适配器模式,是不是很简单?
Array.prototype.max = function () {

    return Math.max.apply(-Infinity, this);
}
console.log([1, 2, 3, 67, 8, 19, 55].max());

享元模式

  享元模式这个词听起来比较费解,什么是享元?其实享元看似不太好理解,但如果将享元拆解成分享单元也许就比较好理解了。
  分享单元又是怎么讲?简单的说,某个单元可以被反复的利用,可以被不同的情形分享,享元模式是性能优化一个很重要也很常见到的机制。比方说,
  某一班组织春游,如果不采用享元模式会怎样?可能就是班长报个终点,大家各自开车或做出租车去,而这显然不是大多时候的做法,更多时候,我们会先到某个点集合,再乘坐大巴过去。大巴虽说也是车,但可以你坐,同时也可以分享给你坐。同样的,我分享给你一个idea,你分享给我一个idea,那我们就拥有两个idea。这就是分享,而对于单元来说比如大巴,比如idea,它们并没有任何的变化。
  享元模式通常被应用在性能的优化上,我们最常见的steam(比如大文件转成流上传或下载),消息队列等等,队列的消息怎么推送和消费,文件的数据包如何打包跟解析,这些单元都是固定了的,不变的,变的是数据包和消息的内容,这种情况下假如不采用享元模式,一个大文件得吃多少内存?一个消息得配一个推送和消费的机制,那整个系统还能干别的?不太可能。
  享元模式的应用在前端也非常常见,很简单的一个prototype,一个原型与继承,就是一个享元模式最直观的体现。要它就new一下它,它还是它,但我们都能用。当然,也许JavaScript中目前越来越明显的颗粒化,柯里化趋势其实也是享元模式应用越来越广泛的体现吧。
  最后也来看一段享元模式的代码吧,应用是男女模特试穿40种(男女各20种)不同款式的衣服。
  不采用享元模式时,需要男女模特各20名,商家简直是有钱没处花。
  它的代码是这样:

//  非享元
export class unFlyweight {

    constructor(sex, clothestType) {
        
        this.sex = sex;
        this.clothestType = clothestType;
    }

    output() {

        return `我是${ this.sex }模特,我穿了${ this.clothestType }类型的衣服`;
    }
}

  采用享元模式,只需要男女各1名模特就够了。

//  享元
export class Flyweight {

    constructor(sex) {
        
        this.sex = sex;
    }
    isManOrWomen() {
        return `我是${this.sex}模特`;
    }
    output() {
        return this.isManOrWomen() + `,我穿了${ this.clothestType }类型的衣服`;
    }
}

  看一下最终的调用:

//  非享元时
let clothesTypeMan = ['卫衣', '衬衫', ...],
    clothesTypeWomen = ['连衣裙', '百褶裙', ...];

for(let i = 0; i < 20; i ++) {

    console.log(new unFlyweight('男', clothesTypeMan[i]));
}
for(let i = 0; i < 20; i ++) {

    console.log(new unFlyweight('女', clothesTypeWomen[i]));
}

  享元时:

//	享元时
let clothesTypeMan = ['卫衣', '衬衫', ...],
    clothesTypeWomen = ['连衣裙', '百褶裙', ...];

let isMan = new Flyweight('男'),
    isWomen = new Flyweight('女');

for(let i = 0; i < 20; i ++) {
    isMan.clothestType = clothesTypeMan[i];
    console.log(isMan.output());
}
for(let i = 0; i < 20; i ++) {

    isWomen.clothestType = clothesTypeWomen[i];
    console.log(isWomen.output());
}

  这几个模式的应用相信看来本文的伙伴们也能感受到他们应用的广泛,也能感受到它的魅力,可能很多时候不知不觉中我们已经在用了,只是我们暂时还不知道这样的方案叫做什么名字,仅此而已。

点击查看更多内容
7人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
1.3万
获赞与收藏
1519

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消