js设计模式学习之单例模式和工厂模式

151 阅读6分钟

单例模式

前言:在工作两年的这个阶段,书写的代码越来越多,代码的可维护性变得无比重要,于是在2020的尾声,我决定将设计模式系统的学习一遍,夯实自己的基础

单例模式的定义:

在传统面向对象的语言中,单例就是保证一个类,只有一个实例,如果存在就直接返回,如果不存在就创建了在返回,确保一个类只有一个实例对象。但是由于javascript语言的限制,单例模式在js中,则是作为一个命名空间的提供者,从全局的空间里,只提供一个单一入口来访问变量

有这样一个需求:

如下图,左边一个输入框,右边一个计数器,计数器实时显示输入框的字数长度

来看一段基本的实现

刚入行的前端工程师的实现方案,代码完全没有经过组织,暴露在全局的变量很多,如果代码量增大的时候,毫无疑问有很多隐藏的bug

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>test</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  <script>
    $(function() {

      var input = $('#J_input');

      //用来获取字数
      function getNum(){
        return input.val().length;
      }

      //渲染元素
      function render(){
        var num = getNum();

        //没有字数的容器就新建一个
        if ($('#J_input_count').length == 0) {
          input.after('<span id="J_input_count"></span>');
        };

        $('#J_input_count').html(num+'个字');
      }

      //监听事件
      input.on('keyup',function(){
        render();
      });

      //初始化,第一次渲染
      render();


    })
  </script>
</head>
<body>
<input type="text" id="J_input"/>
</body>

用单例模式对代码的改造

设置一个全局的变量作为命名空间,提供程序唯一访问入口

var textCount = {
  input:null,
  init:function(config){
    this.input = $(config.id);
    this.bind();
    //这边范围对应的对象,可以实现链式调用
    return this;
  },
  bind:function(){
    var self = this;
    this.input.on('keyup',function(){
      self.render();
    });
  },
  getNum:function(){
    return this.input.val().length;
  },
  //渲染元素
  render:function(){
    var num = this.getNum();

    if ($('#J_input_count').length == 0) {
      this.input.after('<span id="J_input_count"></span>');
    };

    $('#J_input_count').html(num+'个字');
  }
}

$(function() {
  //在domready后调用
  textCount.init({id:'#J_input'}).render();
})

优化是没有止境的

思考?上述代码中,利用单例模式,已近初步的规范了我们的代码,隔离了作用域,避免了全局变量滥用的情况,由于js语言的特性,还需要后续加强的是对私有变量的隔离

工厂模式的定义

与创建型模式类似,工厂模式创建对象(视为工厂里的产品)时无需指定创建对象的具体类。 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

简单工厂模式的示例

登陆的信息中,有不用的角色,根据不同的角色创建不同的对象,下方的模式中是实现的一个简单的工厂模式示例。函数factory接受一个参数,根据参数来创建不同的类型。

// 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);

示例优化改造

 // 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);

总结来说,所谓的工厂模式,就是一个万能的磨具,塞进去什么,就能得到什么加工之后的产物

工厂方法模式

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

show code

 // 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是否属于工厂,也就是做了分支判断,这个工厂只做我能生产的产品,如果你的产品我目前做不了,请找其他工厂代加工;

抽象工厂模式

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

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

   //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 new Error('抽象方法不能调用');
        return this.name;    
      }
    }
    
    //键盘抽象类
    agency.KeyboardShop = function() {
      this.type = '键盘';
    }
    agency.KeyboardShop.prototype = {
      getName: function(name) {
        // return new Error('抽象方法不能调用');
        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); //联想 键盘
   

工厂模式总结

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

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

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