使用JavaScript学习设计模式(1)| 小册免费学

576 阅读9分钟

系列文章

前言

去年的时候先是看了修言大佬的性能优化掘金小册子,收获良多。

之后紧接着买了这本JavaScript 设计模式核⼼原理与应⽤实践,刚好最近有小册免费学的活动,就赶紧把这篇笔记整理出来了,并且补充了小册子中的没有写到的其余设计模式,学习过程中结合 JavaScript 编写的例子,以便于理解和加深印象。

与其说是一篇文章,其实更像是一篇总结性质的学习笔记。

为什么要学习设计模式?

学习之前,先了解什么是设计模式?

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

简答理解 它是一套被反复使用、多人知晓的、经过分类的、代码设计经验总结。

烹饪有菜谱,游戏有攻略,每个领域都存在一些能够让我们又好又快地达成目标的“套路”。在程序世界,编程的“套路”就是设计模式。

学习它也就是学习这个编程世界的套路,对以后升级打怪打装备有很大的帮助。在瞬息万变的前端领域,设计模式也是一种“一次学习,终生受用”知识。

设计模式的原则

描述一个不断发生的重复的问题,以及该问题的解决方案的核心。 这样,你就能一次又一次的使用该方案而不必做重复劳动。

一大法则:

  • 迪米特法则:又叫最少知识法则,一个软件实体应该尽可能少的语其他实体发生相互作用,每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

五大原则:

  • 单一职责原则:一个类,应该仅有一个引起它变化的原因,简而言之,就是功能要单一。
  • 开放封闭原则:对扩展开放,对修改关闭。
  • 里氏替换原则:基类出现的地方,子类一定出现。
  • 接口隔离原则:一个借口应该是一种角色,不该干的事情不敢,该干的都要干。简而言之就是降低耦合、减低依赖。
  • 依赖翻转原则:针对接口编程,依赖抽象而不依赖具体。

JavaScript 中常用的是单一功能和开放封闭原则。

高内聚和低耦合

通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。我们使用设计模式最终的目的是实现代码的 高内聚 和 低耦合。

举例一个现实生活中的例子,例如一个公司,一般都是各个部门各司其职,互不干涉。各个部门需要沟通时通过专门的负责人进行对接。

在软件里面也是一样的 一个功能模块只是关注一个功能,一个模块最好只实现一个功能,这个是所谓的内聚

模块与模块之间、系统与系统之间的交互,是不可避免的, 但是我们要尽量减少由于交互引起的单个模块无法独立使用或者无法移植的情况发生, 尽可能多的单独提供接口用于对外操作, 这个就是所谓的低耦合

封装变化

在实际开发过程中,不发生变化的代码基本是不存在的,所以我要将代码的变化最小化。

设计模式的核心就是去观察你整个逻辑里的变与不变,然后将不变分离,达到使变化的部分灵活、不变的地方稳定的目的。

设计模式的种类

常用的可以分为创建型、结构型、行为型三类,一共 23 种模式。

创建型:

结构型:

行为型:

创建型

工厂模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。 在 JS 中其实就是借助构造函数实现。

例子

某个班级要做一个录入系统,录入一个人,就要写一次。

let liMing = {
  name: "李明",
  age: 20,
  sex: "男",
};

如果多个录入,则可以创建一个类。

class Student {
  constructor(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
}
let zhangSan = new Student("张三", 19, "男");

工厂模式是将创建对象的过程单独封装,使用使只需要无脑传参就行了,就像一个工厂一样,只要给够原料,就可以轻易的制造出成品。

小结

  • 构造函数和创建者分离,对 new 操作进行封装
  • 符合开放封闭原则

单例模式

单例模式的定义:保证一个类仅有一个实例,并且提供一个访问它的全局变量。

实现的方法为前判断实例是否存在,如果存在直接返回,不存在则创建在返回,这就确保了一个类只有一个实例对象。

比如:Vuex、jQuery

例子

使用场景:一个单一对象,比如:弹窗,无论点击多少次,弹窗只应被创建一次,实现起来也很简单,用一个变量缓存就行了。

【点击查看Demo】:单例模式-在线例子

如上面这个弹框,只有在第一次点击按钮时才会创建弹框,之后都不会在创建,而是使用之前创建的弹框。

如此,便是实现了一个应用于单例模式的弹框。

小结

  • 维持一个实例,如果已经创建,就直接返回
  • 符合开放封闭原则

原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

例子

在 JavaScript 中,实现原型模式是在 ECMAscript5 中,提出的 Object.create 方法,使用现有的对象来提供创建的对象__proto__

var prototype = {
  name: "Jack",
  getName: function() {
    return this.name;
  },
};

var obj = Object.create(prototype, {
  job: {
    value: "IT",
  },
});

console.log(obj.getName()); // Jack
console.log(obj.job); // IT
console.log(obj.__proto__ === prototype); //true

有原型就有原理性了

构造器模式

在面向对象的编程语言中,构造器是一个类中用来初始化新对象的特殊方法。并且可以接受参数用来设定实例对象的属性的方法

function Car(model, year, miles) {
  this.model = model;
  this.year = year;
  this.miles = miles;
  // this.info = new CarDetail(model)
  // 属性也可以通过 new 的方式产生
}

// 覆盖原型对象上的toString
Car.prototype.toString = function() {
  return this.model + " has done " + this.miles + " miles";
};

// 使用:
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString()); // Honda Civic has done 20000 miles
console.log(mondeo.toString()); // Ford Mondeo has done 5000 miles

其实就是利用原型链上被继承的特性,实现了构造器。

抽象工厂模式

抽象工厂模式(Abstract Factory)就是通过类的抽象使得业务适用于一个产品类簇的创建,而不负责某一类产品的实例。

JS 中是没有直接的抽象类的,abstract 是个保留字,但是还没有实现,因此我们需要在类的方法中抛出错误来模拟抽象类,如果继承的子类中没有覆写该方法而调用,就会抛出错误。

const Car = function() {};
Car.prototype.getPrice = function() {
  return new Error("抽象方法不能调用");
};

面向对象的语言里有抽象工厂模式,首先声明一个抽象类作为父类,以概括某一类产品所需要的特征,继承该父类的子类需要实现父类中声明的方法而实现父类中所声明的功能:

/**
 * 实现subType类对工厂类中的superType类型的抽象类的继承
 * @param subType 要继承的类
 * @param superType 工厂类中的抽象类type
 */
const VehicleFactory = function(subType, superType) {
  if (typeof VehicleFactory[superType] === "function") {
    function F() {
      this.type = "车辆";
    }
    F.prototype = new VehicleFactory[superType]();
    subType.constructor = subType;
    subType.prototype = new F(); // 因为子类subType不仅需要继承superType对应的类的原型方法,还要继承其对象属性
  } else throw new Error("不存在该抽象类");
};
VehicleFactory.Car = function() {
  this.type = "car";
};
VehicleFactory.Car.prototype = {
  getPrice: function() {
    return new Error("抽象方法不可使用");
  },
  getSpeed: function() {
    return new Error("抽象方法不可使用");
  },
};
const BMW = function(price, speed) {
  this.price = price;
  this.speed = speed;
};
VehicleFactory(BMW, "Car"); // 继承Car抽象类
BMW.prototype.getPrice = function() {
  // 覆写getPrice方法
  console.log(`BWM price is ${this.price}`);
};
BMW.prototype.getSpeed = function() {
  console.log(`BWM speed is ${this.speed}`);
};
const baomai5 = new BMW(30, 99);
baomai5.getPrice(); // BWM price is 30
baomai5 instanceof VehicleFactory.Car; // true

通过抽象工厂,就可以创建某个类簇的产品,并且也可以通过 instanceof 来检查产品的类别,也具备该类簇所必备的方法。

来自九旬的原创:博客原文链接

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情