Skip to content

设计模式

导航目录

设计 S O L I D 五大设计原则

功能说明:SOLID 是面向对象设计中的五个核心原则,有助于编写可维护、可扩展的代码。

单一责任原则 S

定义:一个程序只做好一件事,如果功能过于复杂就拆分,每个部分保持独立。

开放封闭原则 O

定义:对扩张开发,对修改封闭。增加需求时,扩张新代码,而非修改已有代码。这是软件设计的终极目标。

里氏替换原则 L

定义:子类能覆盖父类,父类能出现的地方子类就能出现。JS 中使用较少(弱类型 & 继承使用较少)。

接口独立原则 I

定义:保持接口的单一独立,避免出现 "胖接口"。JS 中没有接口(typescript 例外),使用较少。类似于单一职责原则,这里更关注接口。

依赖倒置原则 D

定义:面向解扣子编程,依赖于抽象而不依赖于具体。使用方只关注接口而不关注具体类的实现。

设计原则总结

  • S O 体现较多,详细介绍
  • L I D 体现较少,但是要了解其用意

用 Promise 来说明 S-O

功能说明:通过 Promise 的链式调用,可以很好地体现单一责任原则和开放封闭原则。

  • 单一责任原则:每个 then 中的逻辑只做好一件事
  • 开放封闭原则:如果新增需求,扩展 then 链,而不是修改已有代码
js
function loadImg(src) {
  var promise = new Promise(function (resolve, reject) {
    var img = document.createElement("img");
    img.onload = function () {
      resolve(img);
    };
    img.onerror = function () {
      reject("图片加载失败");
    };
    img.src = src;
  });
  return promise;
}

var src = "https://www.imooc.com/static/img/index/logo.png";
var result = loadImg(src);

result
  .then(function (img) {
    console.log("img.width", img.width);
    return img;
  })
  .then(function (img) {
    console.log("img.height", img.height);
  })
  .catch(function (err) {
    console.error(err);
  });

23 种设计模式介绍

功能说明:设计模式是在软件设计中反复使用的、经过分类和总结的代码设计经验。根据其目的和功能,可分为三大类。

设计模式分类

  • 创建型:负责对象的创建过程
  • 结构型:处理类或对象的组合关系
  • 行为型:描述对象之间的通信和职责分配

创建型模式

  • 工厂模式(工厂方法模式、抽象工厂模式、建造者模式)
  • 单例模式
  • 原型模式:通过一个现有的对象,用拷贝的方式来生成另一个新的对象

结构型模式

  • 适配器模式
  • 装饰器模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

行为型模式

  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 迭代器模式
  • 职责链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介模式
  • 解释器模式

工厂模式

功能说明:工厂模式将对象的创建过程封装起来,使得客户端不需要直接使用 new 关键字创建对象,而是通过工厂方法来获取对象。

原理

  • 将 new 操作单独封装
  • 遇到 new 时,就要考虑是否该使用工厂模式

示例

  • 你去购买汉堡,直接点餐、取餐,不会自己亲手做 商店要 "封装" 做汉堡的工作,做好直接给顾客

场景

  • jQuery - $('div')
  • React - React.createElement
  • Vue - 异步组件
js
class Utils {
  constructor(name) {
    this.name = name;
  }

  int1() {
    console.log("int1");
  }

  int2() {
    console.log("int2");
  }

  int3() {
    console.log("int3");
  }
}

class Factory {
  create(name) {
    return new Utils(name);
  }
}

class FactoryTo {
  constructor(name) {
    return new Utils(name);
  }
}

const factory = new Factory().create("utils");
factory.int1();
factory.int2();
factory.int3();

const factoryTo = new FactoryTo("utils");
factoryTo.int1();
factoryTo.int2();
factoryTo.int3();

单例模式

功能说明:单例模式确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。

定义

系统中仅被唯一使用的,一个类只有一个实例

示例

  • 登录框
  • 购物车
  • Vuex 和 Redux 中的 store

代码示例

js
class Singleton {
  constructor(name) {
    this.name = name;
  }

  init() {
    console.log(`${this.name}-init`);
  }
}

Singleton.getInstance = (function () {
  let instance;
  return function () {
    if (!instance) {
      instance = new Singleton("单例");
    }
    return instance;
  };
})();

let a = Singleton.getInstance();
let b = Singleton.getInstance();
a.init();
b.init();

console.log(a === b); // true

登录框示例

js
class LoginForm {
  constructor() {
    this.state = "hidden";
  }

  show() {
    if (this.state == "show") {
      console.log("已经显示了");
      return;
    }
    this.state = "show";
    console.log("显示了");
  }

  hidden() {
    if (this.state == "hidden") {
      console.log("已经隐藏了");
      return;
    }
    this.state = "hidden";
    console.log("隐藏了");
  }
}

LoginForm.getInstance = (function () {
  let instance;
  return function () {
    if (!instance) {
      instance = new LoginForm();
    }
    return instance;
  };
})();

const loginForm = LoginForm.getInstance();
loginForm.show();
loginForm.show();
loginForm.hidden();
loginForm.show();

适配器模式

功能说明:适配器模式用于解决接口不兼容的问题,通过中间层(适配器)将一个接口转换为另一个接口,使得原本不兼容的类可以一起工作。

定义

当接口格式和使用者不兼容时,中间加一个适配器接口

场景

  • 封装旧接口
  • Vue 的 computed

代码示例

js
class Adapter {
  standard() {
    return "德国标准插头";
  }
}

class Target {
  constructor() {
    this.adapter = new Adapter();
  }

  conversion() {
    const info = this.adapter.standard();
    console.log(`${info}>>>转换中国插头`);
  }
}

const target = new Target();
target.conversion();

装饰器模式

功能说明:装饰器模式允许在不改变现有对象结构的情况下,动态地给对象添加新的功能。

定义

不改变现有的功能,添加新的功能

代码示例

js
class Circle {
  draw() {
    console.log("画圆");
  }
}

class Decorator {
  constructor(circle) {
    this.circle = circle;
  }

  draw() {
    this.circle.draw();
    this.setColor();
  }

  setColor() {
    console.log("设置颜色");
  }
}

const circle = new Circle();
const decorator = new Decorator(circle);
circle.draw(); // 原始功能
decorator.draw(); // 增强功能

ES7 装饰器

功能说明:ES7 装饰器是一种语法糖,用于修改类、方法或属性的行为,本质上是一个函数。

装饰器的参数

  • target:被装饰的目标对象
  • key:被装饰的属性或方法名
  • decorator:被装饰的属性描述符

装饰器的类型

  • 类装饰器:装饰在类上,target 就是类本身,新增的属性和方法都会挂载到类的静态属性上
  • 方法装饰器:装饰在方法上,target 是类的构造函数,key 是函数名称,decorator 是对函数的约束
  • 属性装饰器:装饰在属性上,target 是类的构造函数,key 是属性名称,decorator 是对属性的约束

装饰器的执行顺序

  • 靠近类函数的先执行(由内向外)
  • 如果由函数包装后返回的装饰器,装饰器的执行顺序不变,函数是从上到下执行

代码示例

类装饰器

js
// target 其实就是被装饰的类本身,isAnimal 会挂载到静态属性上
function isAnimal(target) {
  target.isAnimal = true;
}

@isAnimal
class Monkey {}

console.log(Monkey.isAnimal); // true

// 工厂模式的装饰器
function isAnimalTo(isAnimal) {
  return function (target) {
    target.isAnimal = isAnimal;
  };
}

@isAnimalTo(true)
class MonkeyTo {}

console.log(MonkeyTo.isAnimal); // true

增加新功能

js
function mixins(list) {
  return function (target) {
    Object.assign(target.prototype, list);
  };
}

const foo = {
  add: function () {
    console.log("add");
  },
};

@mixins(foo)
class Utils {}

const utils = new Utils();
utils.add(); // add

方法装饰器

js
function addLog(target, key, decorator) {
  const originalMethod = decorator.value;
  decorator.value = function (...args) {
    console.log(`${key} called with:`, args);
    return originalMethod.apply(this, args);
  };
  return decorator;
}

class Utils {
  @addLog
  static add(a, b) {
    console.log(a + b);
    return a + b;
  }
}

Utils.add(10, 10); // add called with: [10, 10] /n 20

代理模式

功能说明:代理模式为对象提供一个代理,通过代理控制对原对象的访问,类似于生活中的中介。

定义

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

示例

  • 假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我的时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了

代码示例

js
class RealImg {
  constructor(fileName) {
    this.fileName = fileName;
    this.loadFromDisk();
  }

  display() {
    console.log(`loading>display`);
  }

  loadFromDisk() {
    console.log(`loading${this.fileName}>加载硬盘`);
  }
}

class ProxyImg {
  constructor() {
    this.realImg = new RealImg("realImg");
  }

  display() {
    this.realImg.display();
  }
}

class Client {
  constructor() {
    this.proxyImg = new ProxyImg();
  }
}

const client = new Client();
client.proxyImg.display();

ES6 代理

功能说明:ES6 提供了原生的 Proxy 构造函数,用于创建对象的代理,实现对对象的拦截和自定义操作。

代码示例

js
const star = {
  name: "刘德华",
  age: 55,
  phone: "17734444444",
};

let agent = new Proxy(star, {
  set: function (target, key, value) {
    if (key == "price") {
      if (+value < 2000000) {
        throw new Error("不能少于2000000");
      } else {
        console.log("成交");
        target[key] = value;
        return true;
      }
    }
  },
  get: function (target, key) {
    if (key == "phone") {
      return "17788888888";
    }
    if (key == "price") {
      return 2000000;
    }
    return target[key];
  },
});

console.log(agent.phone); // 17788888888
console.log(agent.price); // 2000000
agent.price = 200000000; // 成交

代理模式 VS 适配器模式

功能说明:代理模式和适配器模式都是结构型设计模式,但它们的目的和实现方式有所不同。

  • 适配器模式:提供一个不同的接口
  • 代理模式:提供一个相同的接口

代理模式 VS 装饰器模式

功能说明:代理模式和装饰器模式都是为对象添加额外的功能,但它们的实现方式和目的有所不同。

  • 装饰器模式:扩展功能,原来的功能不变
  • 代理模式:显示原来的功能,但是是经过处理过的

外观模式

功能说明:外观模式为子系统中的一组接口提供了一个一致的高层接口,使得子系统更加容易使用。

定义

外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口使得这一子系统更加容易使用

核心思想

  • 为子系统提供一个上层接口方便使用

代码示例

js
var addMyEvent = function (el, ev, fn) {
  if (el.addEventListener) {
    el.addEventListener(ev, fn, false);
  } else if (el.attachEvent) {
    el.attachEvent("on" + ev, fn);
  } else {
    el["on" + ev] = fn;
  }
};

观察者模式(发布/订阅)

功能说明:观察者模式定义了对象间的一种一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。

定义

发布者被称为可观察对象,订阅者可以订阅,当对象发生改变时,会触发订阅者更新(1 对 N)

场景

  • 网页点击事件
  • Promise
  • Node 自定义事件
  • Vue/React 生命周期

代码示例

js
// 被观察者
class Subject {
  constructor() {
    this.state = 0;
    this.observers = [];
  }

  setState(state) {
    this.state = state;
    this.notifyAllObservers();
  }

  getState() {
    return this.state;
  }

  attach(observer) {
    this.observers.push(observer);
  }

  notifyAllObservers() {
    this.observers.forEach((observer) => {
      observer.update();
    });
  }

  removeObserver(observer) {
    this.observers = this.observers.reduce((temp, ob) => {
      if (observer !== ob) {
        return temp.concat(ob);
      }
      return temp;
    }, []);
  }
}

// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.attach(this);
  }

  update() {
    console.log(`${this.name}=>${this.subject.getState()}`);
  }
}

const subject = new Subject();
const observer1 = new Observer("observer1", subject);
const observer2 = new Observer("observer2", subject);
const observer3 = new Observer("observer3", subject);

subject.setState(100); // 所有观察者都会收到通知

subject.removeObserver(observer1);
subject.setState(200); // 只有 observer2 和 observer3 会收到通知

迭代器模式

功能说明:迭代器模式提供了一种方法来顺序访问一个集合对象的元素,而不暴露其内部表示。

定义

顺序访问一个集合,使用者无需知道集合内部结构

代码示例

js
class Iterator {
  constructor(container) {
    this.list = container.list;
    this.index = 0;
  }

  next() {
    if (this.hasNext()) {
      return this.list[this.index++];
    }
  }

  hasNext() {
    return this.index < this.list.length;
  }
}

class Container {
  constructor(list) {
    this.list = list;
  }

  getIterator() {
    return new Iterator(this);
  }
}

const container = new Container([1, 2, 3, 4, 5]);
const iterator = container.getIterator();
while (iterator.hasNext()) {
  console.log(iterator.next());
}

ES6 迭代器

迭代器是一种特殊对象,所有的迭代器对象都有一个 next() 方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是 value,表示下一个将要返回的值;另一个是 done,它是一个布尔类型的值,当没有更多可返回数据时返回 true。

js
function createIterator(items) {
  var i = 0;
  return {
    next: function () {
      var done = i == items.length;
      var value = !done ? items[i++] : undefined;
      return {
        done: done,
        value: value,
      };
    },
  };
}

var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

状态模式

功能说明:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

定义

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象

核心思想

  • context 对象,随着状态的改变而改变

代码示例

js
class Context {
  constructor() {
    this.state = null;
  }

  getState() {
    console.log(this.state);
    return this.state;
  }

  setState(state) {
    this.state = state;
  }
}

class State {
  constructor(color) {
    this.color = color;
  }

  handle(context) {
    context.setState(this);
  }
}

const context = new Context();
const red = new State("红");
const yellow = new State("黄");
const green = new State("绿");

red.handle(context);
context.getState(); // 红

yellow.handle(context);
context.getState(); // 黄

green.handle(context);
context.getState(); // 绿

原型模式

功能说明:原型模式通过复制现有对象来创建新对象,而不是通过实例化类来创建。

定义

克隆一个对象,生成一个新的对象

代码示例

js
const p = {
  getName: function () {
    console.log(this.name);
  },
};

const oa = Object.create(p);
oa.name = "tom";
oa.getName(); // tom

const ob = Object.create(p);
ob.name = "kiss";
ob.getName(); // kiss

桥接模式

功能说明:桥接模式将抽象部分与实现部分分离,使它们可以独立地变化。

定义

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦

核心思想

  • 这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响

代码示例

js
class Color {
  constructor(color) {
    this.color = color;
  }
}

class Shape {
  constructor(name, color) {
    this.name = name;
    this.color = color;
  }

  draw() {
    console.log(this.color.color);
  }
}

const red = new Color("红色");

const shape = new Shape("Shape", red);
shape.draw(); // 红色

组合模式

功能说明:组合模式生成树结构,表示"整体-部分"关系,让整体和部分都具有一致的操作方式。

定义

生成树结构,表示"整体-部分"关系,让整体和部分都具有一致的操作方式

示例

  • Vue 的虚拟 DOM - VNode
  • 菜单结构

享元模式

功能说明:享元模式通过共享对象来减少内存使用,适用于大量相似对象的场景。

定义

  • 共享内存(主要是内存,而非效率)
  • 相同的数据,共享使用

策略模式

功能说明:策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。

定义

不同的策略分开处理,避免大量的 if...else

代码示例

js
// 超市会员打折
class OrdinaryUser {
  buy() {
    console.log("普通用户9.8折");
  }
}

class MembersUser {
  buy() {
    console.log("会员用户9.5折");
  }
}

class VipUser {
  buy() {
    console.log("VIP用户9.0折");
  }
}

const ordinaryUser = new OrdinaryUser();
const membersUser = new MembersUser();
const vipUser = new VipUser();

ordinaryUser.buy();
membersUser.buy();
vipUser.buy();

模板模式

功能说明:模板模式定义了一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下重定义算法的某些步骤。

定义

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

责任链模式

功能说明:责任链模式将请求的发送者和接收者解耦,通过将多个处理对象连成一条链,使请求沿着这条链传递,直到被处理。

定义

  • 一步操作可以分为多个职责角色来完成
  • 把这些角色都分开,然后用一个链串起来
  • 将发起者和各处理者进行隔离

示例

  • jQuery 链式操作
  • Promise.then()

代码示例

js
class Action {
  constructor(name) {
    this.name = name;
    this.nextAction = null;
  }

  setNextAction(action) {
    this.nextAction = action;
  }

  handle() {
    console.log(`${this.name}审批`);
    if (this.nextAction != null) {
      this.nextAction.handle();
    }
  }
}

const a1 = new Action("组长");
const a2 = new Action("经理");
const a3 = new Action("总经理");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();

命令模式

功能说明:命令模式将请求封装为对象,使请求的发送者和接收者解耦,并且可以参数化、队列化和记录请求。

定义

  • 执行命令时,发布者和执行者分开
  • 中间加入命令对象,作为中转站

代码示例

js
class Receiver {
  exec() {
    console.log("执行");
  }
}

class Command {
  constructor(receiver) {
    this.receiver = receiver;
  }

  cmd() {
    console.log("触发命令");
    this.receiver.exec();
  }
}

class Invoker {
  constructor(command) {
    this.command = command;
  }

  invoker() {
    console.log("命令");
    this.command.cmd();
  }
}

// 士兵
const receiver = new Receiver();
// 号手
const command = new Command(receiver);
// 将军
const invoker = new Invoker(command);
invoker.invoker();

访问者模式

功能说明:访问者模式将数据操作和数据结构分离,使得可以在不修改数据结构的情况下定义新的操作。

定义

将数据操作和数据结构进行分离

解释器模式

功能说明:解释器模式定义了一个语言的语法规则,并提供了解释器来解释该语言中的表达式。

定义

这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等

备忘录模式

功能说明:备忘录模式允许在不破坏封装性的情况下,捕获和恢复对象的内部状态。

定义

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销功能)

示例

  • 编辑器