vlambda博客
学习文章列表

JS设计模式(二):工厂模式、单例模式和适配器模式

软件开发的历史,就是和bug斗争的历史。

前言

这个世纪的哲学将会成为下一个世纪的常识 」这是《Linux/Unix设计思想》中提到的。「良好的程序员写出优秀的软件,优秀的程序员“窃取”优秀的软件 」,当然不是鼓励我们去粘贴复制,而是让我们不仅仅是粘贴复制,还要转化成为「 自己的东西 」。 这期接着聊。

工厂模式

工厂模式简单的理解就是「 对象创建的封装 」,为什么对new出来的对象进行封装呢?

抽象工厂的概念是为了把一些细节的东西封装起来,让开发者把重心移到该怎么用,而不用过多关注于怎么实现。举一个简单的例子:我们不用考虑系统内核的具体现实,我们只需要考虑编程语言怎么用,代码怎么写。

工厂模式的场景很多,例如这样:

(function(global, factory) {

  typeof exports === 'object'

    && typeof module !== 'undefined' ?

        module.exports = factory() :

          typeof define === 'function'

        && define.amd ? define(factory) :

           (global.UploadClient = factory());

}(this, function() {

        //...

        return {

           //...

        }

})

我们常常创建一个自调用函数,把全局对象传进去,把我们的工厂挂载到上面,方便我们调用。

如果上面的例子还不明确,我们再看看其他场景:

在jQ中就是用的工厂模式:

//模拟一个jQ的实现

class jQuery {

    constructor(selector) {

        let dom = [].slice.call(document.querySelectorAll(selector));

        let len = dom ? dom.length : 0;

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

            this[i] = dom[i];

        }

        this.length = len;

        this.selector = selector || "";

    }

    append(node) {}

    addClass(name) {}

    html(data) {}

    //...

}

window.$ = function (selector) {

    return new jQuery(selector);

};

在react中也用到了工厂模式:

//简单的模拟react中createElement

class Vnode {

    constructor(tag, attrs, children) {

    //....

    }

}

React.createElement = function (tag, attrs, children) {

    return new Vnode(tag, attrs, children);

};

工厂模式实现了"构造者"与"创建者"的分离。符合开放封闭原则,只能使用,无法修改。

我们举一个简单的商品与工厂的例子,来用代码实现一次:

class Product {

    constructor(name) {

        this.name = name;

    }

    init() {

        console.log("init");

    }

    add() {

        console.log("add");

    }

}


class Creater {

    create(name) {

        return new Product(name);

    }

}


let creater = new Creater();

let p = creater.create("p1");

p.init();

单例模式

单例模式就是一个类只能有一个实例化对象。通常情况下,我们创建的类,可以实例化很多对象来满足需求,但是有的需求我们只需一个实例化对象,比如购物车,商品有很多,但购物车只能有一个。

在四人帮(GoF)的书里面,单例模式的应用描述如下:

  • 每个类只有一个实例,这个实例必须通过一个广为人知的接口,来被客户访问。

  • 子类如果要扩展这个唯一的实例,客户可以不用修改代码就能使用这个扩展后的实例。

由于JS是动态类型的语言,我们只能规范,如下:使用时,我们不能去new,而是直接调用

class SingleObject{

    login(){

        console.log("login ....")

    }

}


SingleObject.getInstance=(function(){

    let instance;

    return function(){

        if(!instance){

            instance=new SingleObject()

        }

        return instance

    }

})()


let obj1=SingleObject.getInstance();

obj1.login();


let obj2=SingleObject.getInstance();

obj2.login();


console.log(obj1===obj2)

//true


//无法控制,只能靠规范约束

let obj3=new SingleObject();

obj3.login();

使用单例模式的场景还有登录、以及vuex和redux中的store等等。

单例模式符合单一职责原则,只实例化唯一的对象,保证唯一性。

同样单例模式不可避免的带来一些弊端,单例模式更难测试,因为可能有多种多样的问题出现,例如隐藏的依赖关系,很难去创建多个实例,很难清理依赖关系,等等。

适配器模式

适配器模式是将本来不兼容的接口或者类转化为可以通用的方法。

常见的场景就是兼容浏览器的api,比如我们常常封装一些方法来获取样式,但是IE和其他浏览器提供的api是不相同,所以我们需要创建一个公共的方法来处理这些不同平台的接口。

还有一个场景是新需求和旧需求有冲突的情况下,在不改变旧有的功能的基础上,添加新的功能。

比如获取样式:

//兼容IE

function getStyle(ele,attr){

    if(window.getComputedStyle){

        return window.getComputedStyle(ele,false)[attr];

    }else{

        return ele.currentStyle[attr];

    }

}

举一个简单的例子:比如法国的插头要拿到中国用,但是中国的插座与法国的插头不配,我们需要一个适配器来转化。


class Adaptee {

    specificRequest() {

        return "法国标准插头";

    }

}


class Target {

    constructor() {

        this.adaptee = new Adaptee();

    }

    request() {

        let info = this.adaptee.specificRequest();

        return `${info}  ==>  中国标准插头`;

    }

}


let target = new Target();

let res = target.request();