前端须知——创造型模式——单例模式和工厂模式

单例模式

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

数学与逻辑学中,singleton定义为“有且仅有一个元素的集合”。

为什么要用单例模式

  想象一下某些web应用,当点击登录按钮时,会弹出一个登录框,无论你点击多少次这个登录按钮,登录框都只会出现一个,不会出现多个登录框。同时不会频繁的进行删除和添加,而是同一个登录框进行隐藏和显示,因为删除和添加十分耗费性能,所以单例可以达到最大化的效能利用。
  登录框这个例子就是单例模式最典型的应用,符合业务的需求,又能够提高性能

详解

  在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

  JavaScript中没有类的定义,单例模式的特点是”唯一“和”全局访问“,那么我们可以联想到JavaScript中的全局对象,利用ES6的let不允许重复声明的特性,刚好符合这两个特点;是的,全局对象是最简单的单例模式;

let obj = {
    name:"leo",
    getName:function(){}
}

  上述代码中可以知道obj就是一个单例,因为obj刚好就符合单例模式的两大特点:“唯一” 和 “可全局访问”;

  但是我们并不建议这么实现单例,因为全局对象/全局变量会有一些弊端:

  1. 污染命名空间(容易变量名冲突)
  2. 维护时不容易管控 (搞不好就直接覆盖了)

简单版单例模式:

分析:只能有一个实例,所以我们需要使用if分支来判断,如果已经存在就直接返回,如果不存在就新建一个实例;

let Singleton = function(name){
    this.name = name;
    this.instance = null; 
}

Singleton.prototype.getName = function(){
    console.log(this.name);
}

Singleton.getInstance = function(name){
    if(this.instace){
        return this.instance; 
    }
    return this.instance = new Singleton(name);
}

let winner = Singleton.getInstance("winner");   //winner
console.log(winner.getName());
let sunner = Singleton.getInstance("sunner");   //winner
console.log(sunner.getName())

  上面代码中我们是通过一个变量instance的值来进行判断是否已存在实例,如果存在就直接返回this.instance,如果不存在,就新建实例并赋值给instance;

  但是上面的代码还是存在问题,因为创建对象的操作和判断实例的操作耦合在一起,并不符合“单一职责原则”;

改良版

思路:通过一个闭包,来实现判断实例的操作;

let CreateSingleton = (function(){
    let instance = null;
    return function(name){
        this.name = name;
        if(instance){
            return instance
        }
        return instance = this;
    }
})()


CreateSingleton.prototype.getName = function(){
        console.log(this.name);
}

let winner = new CreateSingleton("winner");  //winner
console.log(winner.getName());
let sunner = new CreateSingleton("sunner");  //winner
console.log(sunner.getName())

代理版单例模式

通过代理的形式,将创建对象的操作和实例判断的操作进行解耦拆分,实现更小粒度的划分,符合“单一职责原则”

let ProxyCreateSingleton = (function(){
    let instance = null;
    return function(name){
        if(instance){
            return instance
        }
        return instance = new Singlton(name);
    }
})();

let Singlton = function(name){
    this.name = name;
} 

Singlton.prototype.getName = function(){
    console.log(this.name);
}

let winner = new ProxyCreateSingleton("winner");
console.log(winner.getName());
let sunner = new ProxyCreateSingleton("sunner");
console.log(sunner.getName());

惰性单例模式

我们经常会有这样的场景:页面多次调用都有弹窗提示,只是提示内容不一样;

这个时候我们可以立马想到是单例模式,弹窗就是单例实例,提示内容是参数传递;我们可以用惰性单例模式来实现它;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="loginBtn">Test1</div>
</body>
<script>
    let getSingleton = function (fn) {
        let result;
        return function () {
            return result || (result = fn.apply(this, arguments)); // 确定this上下文并传递参数
        }
    }
    let createAlertMessage = function (html) {
        const div = document.createElement('div');
        div.innerHTML = html;
        div.style.display = 'none';
        document.body.appendChild(div);
        return div;
    }

    let createSingleAlertMessage = getSingleton(createAlertMessage);
    document.getElementById('loginBtn').onclick = function () {
        const alertMessage = createSingleAlertMessage('Test2');
        alertMessage.style.display = 'block';
    }
</script>

</html>

  惰性单例是指的是页面开始加载的时候我们的实例是没有进行创建的,是当我们点击页面的div之后才开始创建实例(按需创建),这可以提高我们的网页性能,加快我们的页面渲染速度;

工厂模式

定义

工厂模式定义创建对象的接口,但是让子类去真正的实例化。也就是工厂方法将类的实例化延迟到子类

  所谓工厂模式就是像工厂一样重复的产生类似的产品,工厂模式只需要我们传入正确的参数,就能生产类似的产品;
  工厂模式根据抽象程度依次分为简单工厂模式、工厂方法模式、抽象工厂模式;

简单工厂模式

  在我们的生活中很多时候就有这样的场景,像在网站中有的页面是需要根据账号等级来决定是否有浏览权限的;账号等级越高可浏览的就越多,反之就越少;

// JS设计模式之简单工厂
function factory(role) {
    function SuperAdmin() {
        this.name = "超级管理员";
        this.viewPage = ["首页", "发现页", "通讯录", "应用数据", "权限管理"];
    }

    function Admin() {
        this.name = "管理员";
        this.viewPage = ["首页", "发现页", "通讯录", "应用数据"];
    }

    function User() {
        this.name = "普通用户";
        this.viewPage = ["首页", "发现页", "通讯录"];
    }

    switch (role) {
        case "SuperAdmin":
            return new SuperAdmin();
            break;

        case "Admin":
            return new Admin();
            break;

        case "User":
            return new User();
            break;
    }
}

let superAdmin = factory("SuperAdmin");
console.log(superAdmin);
let admin = factory("Admin");
console.log(admin);
let user = factory("User");
console.log(user);

  上述代码中,factory就是一个工厂,factory有三个函数分别是对应不同的产品,switch中有三个选项,这三个选项相当于三个模具,当匹配到其中的模具之后,将会new一个构造函数去执行生产工厂中的function;但是我们发现上面的简单工厂模式会有一定的局限性,就是如果我们需要去添加新的产品的时候,我们需要去修改两处位置(需要修改function和switch)才能达到添加新产品的目的;

下面我们将简单工厂模式进行改良:

// JS设计模式之简单工厂改良版
function factory(role) {
    function User(opt) {
        this.name = opt.name;
        this.viewPage = opt.viewPage;
    }

    switch (role) {
        case "superAdmin":
            return new User({
                name: "superAdmin",
                viewPage: ["首页", "发现页", "通讯录", "应用数据", "权限管理"]
            });
            break;

        case "admin":
            return new User({
                name: "admin",
                viewPage: ["首页", "发现页", "通讯录", "应用数据"]
            });
            break;

        case "normal":
            return new User({
                name: "normal",
                viewPage: ["首页", "发现页", "通讯录"]
            });
    }
}

let superAdmin = factory("superAdmin");
console.log(superAdmin);
let admin = factory("admin");
console.log(admin);
let normal = factory("normal");
console.log(normal);

  经过上面的修改之后,我们工厂里面的函数相当于一个万能摸具,switch里面给我什么,我就加工成什么样的;自然就解决了添加商品需要修改两处代码的问题;

工厂方法模式

  工厂方法模式是将创建对象的工作推到子类中进行;也就是相当于工厂总部不生产产品了,交给下辖分工厂进行生产;但是进入工厂之前,需要有个判断来验证你要生产的东西是否是属于我们工厂所生产范围,如果是,就丢给下辖工厂来进行生产,如果不行,要么新建工厂生产要么就生产不了;

// JS设计模式之工厂方法模式
function factory(role) {
    if (this instanceof factory) {
        var a = new this[role]();
        return a;
    } else {
        return new factory(role);
    }
}

factory.prototype = {
    "superAdmin": function() {
        this.name = "超级管理员";
        this.viewPage = ["首页", "发现页", "通讯录", "应用数据", "权限管理"];
    },
    "admin": function() {
        this.name = "管理员";
        this.viewPage = ["首页", "发现页", "通讯录", "应用数据"];
    },
    "user": function() {
        this.name = "普通用户";
        this.viewPage = ["首页", "发现页", "通讯录"];
    }
}

let superAdmin = factory("superAdmin");
console.log(superAdmin);
let admin = factory("admin");
console.log(admin);
let user = factory("user");
console.log(user);

  工厂方法模式关键核心代码就是工厂里面的判断this是否属于工厂,也就是做了分支判断,这个工厂只做我能生产的产品,如果你的产品我目前做不了,请找其他工厂代加工;

  代码比简单工厂模式复杂了,引入了抽象层,还有子工厂,这会增加代码的复杂度和理解难度。但是相比于简单工厂模式,代码的维护性和扩展性提高了,新增产品时,只需要增加对应的产品类和产品工厂类,不需要修改到抽象工厂类和其他子工厂。更加符合面向对象的开放封闭原则。

  当然具体场景具体分析,复杂性和扩展性相比如何舍去,在使用的时候要结合实际场景去分析。

抽象工厂模式

  如果说上面的简单工厂和工厂方法模式的工作是生产产品,那么抽象工厂模式的工作就是生产工厂的;

  举个例子:代理商找工厂进行合作,但是工厂没有实际加工能力来进行代加工某产品;无奈又签署了合同,这时,工厂上面的集团公司就出面了,集团公司承认该工厂是该集团下属公司,所以集团公司就重新建造一个工厂来进行代加工某商品以达到履行合约;

一些关于继承的知识请看 “JavaScript继承——圣杯模式"

//JS设计模式之抽象工厂模式
let agency = function(subType, superType) {
    //判断抽象工厂中是否有该抽象类
    if (typeof agency[superType] === 'function') {
        function F() {};
        //继承父类属性和方法
        F.prototype = new agency[superType]();
        console.log(F.prototype);
        //将子类的constructor指向子类
        subType.constructor = subType;
        //子类原型继承父类
        subType.prototype = new F();

    } else {
        throw new Error('抽象类不存在!')
    }
}

//鼠标抽象类
agency.mouseShop = function() {
    this.type = '鼠标';
}
agency.mouseShop.prototype = {
    getName: function(name) {
        return this.name;
    }
}

//键盘抽象类
agency.KeyboardShop = function() {
    this.type = '键盘';
}
agency.KeyboardShop.prototype = {
    getName: function(name) {
        return this.name;
    }
}

//普通鼠标子类
function Mouse(name) {
    this.name = name;
    this.item = "买我,我线长,玩游戏贼溜"
}
//抽象工厂实现鼠标类的继承
agency(Mouse, 'mouseShop');
//子类中重写抽象方法
// Mouse.prototype.getName = function() {
// return this.name;
// }

//普通键盘子类
function Keyboard(name) {
    this.name = name;
    this.item = "行,你买它吧,没键盘看你咋玩";
}
//抽象工厂实现键盘类的继承
agency(Keyboard, 'KeyboardShop');
//子类中重写抽象方法
// Keyboard.prototype.getName = function() {
// return this.name;
// }

//实例化鼠标
let mouseA = new Mouse('联想');
console.log(mouseA.getName(), mouseA.type, mouseA.item); //联想 鼠标

//实例化键盘
let KeyboardA = new Keyboard('联想');
console.log(KeyboardA.getName(), KeyboardA.type, KeyboardA.item); //联想 键盘

  抽象工厂模式一般用于严格要求以面向对象思想进行开发的超大型项目中,我们一般常规的开发的话一般就是简单工厂和工厂方法模式会用的比较多一些;

总结

白话解释:

  简单工厂模式就是你给工厂什么,工厂就给你生产什么;

  工厂方法模式就是你找工厂生产产品,工厂是外包给下级分工厂来代加工,需要先评估一下能不能代加工;能做就接,不能做就找其他工厂;

  抽象工厂模式就是工厂接了某项产品订单但是做不了,上级集团公司新建一个工厂来专门代加工某项产品;

工厂模式对比其他方法

  1. 工厂类集中了所有对象的创建,便于对象创建的统一管理
  2. 对象的使用者仅仅是使用产品,实现了单一职责
  3. 便于扩展,如果新增了一种业务,只需要增加相关的业务对象类和工厂类中的生产业务对象的方法,不需要修改其他的地方。
  4. 确实违反了开闭原则

工厂模式的优缺点:

优点:

一个调用者想创建一个对象,只要知道其名称就可以了。

扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。

屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:

  每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值