Skip to content

设计模式

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

单一责任原则 S

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

开放封闭原则 O

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

里氏替换原则 L

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

接口独立原则 I

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

依赖倒置原则 D

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

设计原则总结

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

用 Promise 来说明 S-O

  • 单一职责原则:每个 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 时,就要考虑是否该使用工厂模式

示例

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

场景

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

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

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

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

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

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

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

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

单例模式

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

示例

  • 登录框
  • 购物车
  • vuex 和 redux 中的 store
js
class Thesingleton {
  constructor(name) {
    this.name = name;
  }

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

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

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

console.log(a === b); //true
js
class LoginFrom {
  constructor(state) {
    this.state = state;
  }

  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("隐藏了");
  }
}

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

LoginFrom.getInstance.show();
LoginFrom.getInstance.show();
LoginFrom.getInstance.hidden();
LoginFrom.getInstance.show();

适配器模式

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

场景

  • 封装旧接口
  • Vue 的 computed
js
class Adapter {
  standard() {
    return "德国标准插头";
  }
}

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

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

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

装饰器模式

  • 不改变现有的功能,添加新的功能
js
class Circle {
  draw() {
    console.log("画圆");
  }
}

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

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

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

class Client {
  constructor(circle, decorator) {
    this.circle = circle;
    this.decorator = decorator;
  }
}

const circle = new Circle();
const decorator = new Decorator(circle);
const client = new Client(circle, decorator);
client.circle.draw();
client.decorator.draw();

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


// 工厂
function isAnimalTo(isAnimal) {
  return function (target) {
    target.isAnimal = true;
  }
}

@isAnimalTo(true)
class MonkeyTo {

}
console.log(MonkeyTo.isAnimal);
  • 增加新功能
js
function mixins(list) {
  return function (target) {
    return Object.assign(target.prototype, list);
  }
}

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


@mixins(foo)
class Utils {

}

Utils.add();
  • 装饰方法 target=>本类,key=>被装饰的名字,decorator=>被装饰的属性
js
function addLog(target, key, decorator) {
  const m = decorator.value;
  decorator.value = function () {
    console.log(`${key}log`, arguments);
    m.apply(target, arguments);
  }
  return decorator;
}


class Utils {

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


Utils.add(10, 10);

代理模式

  • 代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明

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

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

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

class ProxyImg {
  constructor() {
    this.reallmg = new Reallmg("reallmg");
  }

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

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

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

es6 代理

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);
console.log(agent.price);
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);

迭代器模式

  • 顺序访问一个集合,使用者无需知道集合内部结构
js
class Iterator {
  constructor(container) {
    this.list = container.list;
    this.index = 0;
  }

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

  hasNext() {
    if (this.index == this.list.length) {
      return false;
    }
    return true;
  }
}

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());
}
// 迭代器是一种特殊对象,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,它是一个布尔类型的值,当没有更多可返回数据时返回true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值
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();

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

桥接模式

  • 桥接(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);
  }
}

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("普通打折9.0折");
  }
}

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

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

模板模式

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

责任链模式

  • 一步操作可以分为多个职责角色来完成

  • 把这些角色都分开,然后用一个链串起来

  • 将发起者和各处理者进行隔离

  • Jq 链式操作,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 解析、符号处理引擎等。

备忘录模式

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