JS设计模式(一)-工厂模式、单例模式浅谈

549 阅读7分钟

欢迎关注微信公众号做一个快乐的前端

前言

模式是一种可以复用的解决方案,而设计模式(design pattern)就是把经常用到的一些可重用的代码风格进行系统的分类,统一和分类。

那么理解和熟悉设计模式是很重要的。设计模式有以下三点好处:

  • 给我们开发人员对代码的阅读和编写提供了一种指导思想
  • 可以很容易地进行重用:一个模式是指一种立即可以用地解决方案
  • 模式富有表达力:看到一种模式时,通常就表示有一个设置好的结构和表达解决方案的词汇,以帮助我们非常轻松地表达出相当大的解决方案

设计模式的原则是“找出 程序中变化的地方,并将变化封装起来”,它的关键是意图,而不是结构。 不过要注意,使用不当的话,可能会事倍功半。

目前网上流行的有23种模式,本文只针对JavaScript语言的特性和应用场景,分批次讲解各种模式


1. 工厂模式

工厂模式(Factory Pattern)是用来创建对象的一种最常用的设计模式,我们把具体创建对象的逻辑封装在一个函数中,那么这个函数就是一个“工厂”。专门供创建对象使用。工厂模式根据抽象程度的不同可以分为“简单工厂”、“工厂方法”、与“抽象工厂”。

1.1 简单工厂

什么是简单工厂?举个栗子,假如我们现在想去买西瓜,那么我们就要去关心 它的价格和是否保熟等信息,那么我们关心的这些信息就是我们向“工厂”传入的参数,我们就会买到一个符合我们的西瓜“对象”。

function Watermelon (price) {
    this.price = price;
    this.isRipe = price >= 10;
}
let zs = new Watermelon(5);
// 张三用5块钱买到了一个不保熟的5块钱的西瓜
// {price: 5, isRipe: false}
let ls = new Watermelon(20);
// 李四用20块钱买到了一个绝对保熟的西瓜
// {price: 20, isRipe: true}

由上代码可以看出不管是谁来购买西瓜,只要输入对应的价格,就能返回对应对象。说白了简单工厂就是指我们在一个工厂函数中定义了规则,我们使用的时候不用去管这个函数内部逻辑是什么样的,只需要传入相对应的参数,就能批量得到一组对象。

1.2 工厂方法

什么是工厂方法?工厂模式是对产品类的抽象,使其创建多类产品的实例。比如现在张三不想购买西瓜了,想买冬瓜,南瓜,这个时候工厂方法就体现出来了。

  function Buy (name) {
    function Melon (name, price) {
      this.name = name;
      this.price = price;
      this.isRipe = price >= 10;
    }

    let res = {};
    switch (name) {
    case '西瓜':
      res = new Melon(name, 10);
      break;
    case '南瓜':
      res = new Melon(name, 20);
      break;
    default:
      throw new Error('没有此瓜');
    }
    return res;
  }

  let zsBuyWatermelon = new Buy('西瓜');
  // 张三购买西瓜
  // {name: "西瓜", price: 10, isRipe: true}
  let zsBuyPumpkin = new Buy('南瓜');
  // 张三购买南瓜
  // {name: "南瓜", price: 20, isRipe: true}
  let noBuy = new Buy('随便');
  // throw error

上述代码可以看见,我们针对不同产品类的瓜创建了不同的对象,比如西瓜有西瓜的价格,南瓜有南瓜的价格。

1.3 抽象工厂

什么是抽象工厂?抽象工厂模式就是:就是通过类的抽象使得业务适用于一个产品类簇的创建,而不负责某一类产品的实例。说白了就是针对产品类的进一步抽象,比如沃尔玛商场,里面有水果产品,还有生鲜产品,水果产品中又有西瓜,苹果等,生鲜产品中又有小龙虾,鱼等。那么沃尔玛商场就是一个工厂,产品类就是水果产品和生鲜产品。

  let WalMart = function (subType, superType) {
    if (typeof WalMart[superType] === 'function') {
      // 有的话新建一个过渡函数,指针指向这个方法,就是说有了第二个参数方法的全部属性
      // 然后又让第一个参数指向了过度函数就等于说第二个参数方法的所以属性都被第一个参数继承了
      function F() {}

      F.prototype = new WalMart[superType]();
      subType.constructor = subType;
      subType.prototype = new F();
    } else {
      throw  new Error('抽象类不存在');
    }
  }

  // 这里创建抽象类
  WalMart.fruits = function () {
    this.region = '水果区';
  }
  WalMart.fruits.prototype.getIsRipe = function () {
    return new Error('抽象方法不能调用');
  }

  // 然后去创建一个子类,然后调用抽象方法继承抽象类
  function Fruits (name, price) {
    this.name = name;
    this.price = price;
  }
  WalMart(Fruits, 'fruits');
  Fruits.prototype.getIsRipe = function () {
    return this.price >= 10;
  }
  let zs = new Fruits('西瓜', 10);
工厂模式总结

什么时候使用工厂模式

  • 对象的构建十分复杂
  • 需要依赖不同环境创建不同对象实例时
  • 处理大量具有相同属性的对象时

工厂模式的优点

  • 动态创建对象:可以用于需要在运行时确定对象类型的情况
  • 抽象:封装了对象创建的细节,用户不会接触到对象的构造器,只需要告诉工> 厂需要哪种对象
  • 可用性/可维护性:具有高内聚低耦合的特点

工厂模式缺点

没有解决对象识别的问题(即怎样知道一个对象的类型)。


2. 单例模式

单例模式是一种常用的模式,他的实现在于始终保证一个特定类只能有一个实例化对象,通常实现私立是:将一家生产的对象通过闭包进行维护,下次再次生成新对象时,直接返回上一次对象

  function User (name) {
    this.name = name;
  }
  let Singleton = function (fn) {
    let instance = null;
    return function (args) {
      if (instance) {
        return instance
      }
      return instance = new fn(args);
    }
  }
  let People = Singleton(User);
  let zs = new People('zs');
  let ls = new People('ls');
  console.log(zs === ls); // true
单例模式总结

针对只需要生成一个唯一对象的时候,例如遮罩层,登录浮窗等,我们就可以使用单例模式去实现。

优点

  • 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了
  • 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  • 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

  • 单例模式没有抽象层,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  • 滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;

网上的资料深浅不一,本人也是处在学习的过程中的总结,如果发现错误,欢迎留言指出~

设计模式持续更新中...

扫描二维码添加微信加入前端开发群,分享技术难题。

image.png