# 设计模式

# 设计模式定义

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结

# 设计模式原则

目的:减少耦合,降低复杂度,增强复用性,降低代码的开发维护扩展成本

# 单一职责原则

✖ 不符合单一职责原则

function render() {
  // 1、发请求,获取数据
  // 2、将数据转换为dom
  // 3、dom 插入到 html

  // res:[{id:1,name:'张三'}]
  let html = "<ul>";
  fetch("url2").then(res => {
    const data = [{ id: 1, name: "张三" }, { id: 2, name: "李四" }];
    data.forEach(item => {
      html += `<li>${item.name}</li>`;
    });
    html += "</ul>";
    document.getElementById("root").innerHTML = html;
  });
}
render();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

✔ 符合单一职责原则

function getData() {
  return fetch("url2").then(res => {
    const data = [{ id: 1, name: "张三" }, { id: 2, name: "李四" }];
    return data;
  });
}

function createDOM(data) {
  let html = "<ol>";
  data.forEach(item => {
    html += `<li>${item.name}</li>`;
  });
  html += "</ol>";
  return html;
}

function renderDOM(html) {
  document.getElementById("root").innerHTML = html;
}

async function init() {
  //   获取数据
  const data = await getData();
  // 构建dom
  const dom = createDOM(data);
  // 渲染dom
  renderDOM(dom);
}
init();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 开闭原则

对修改关闭,对拓展开放

初始需求

// 增加新的属性 school
class Student {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getInfo() {
    console.log(this.name, this.age);
  }
}
var student = new Student("张三", 22);
student.getInfo();
1
2
3
4
5
6
7
8
9
10
11
12

增加新需求后

✖ 不符合开闭原则

class Student {
  constructor(name, age, school) {
    this.name = name;
    this.age = age;
    this.school = school;
  }
  getInfo() {
    console.log(this.name, this.age, this.school);
  }
}
var student = new Student("张三", 22);
student.getInfo();

var student2 = new Student("李四", 25, "北京大学");
student2.getInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

✔ 符合开闭原则

class Student {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getInfo() {
    console.log(this.name, this.age);
  }
}

class CollageStudent extends Student {
  constructor(name, age, school) {
    super(name, age);
    this.school = school;
  }
  getInfo() {
    console.log(this.name, this.age, this.school);
  }
}

var student = new Student("张三", 22);
student.getInfo();

var student2 = new CollageStudent("李四", 25, "北京大学");
student2.getInfo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 里氏替换原则

任何基类可以出现的地方,子类一定可以出现

class Reactangle {
  constructor(x, y) {
    this.width = x;
    this.height = y;
  }
  getArea() {
    console.log("矩形的面积是:", this.width * this.height);
  }
  setHeight(y) {
    this.height = y;
  }
}

//   const reactangle = new Reactangle(10, 20);
//   reactangle.getArea();
//   reactangle.setHeight(200)
//   reactangle.getArea();

class Square extends Reactangle {
  constructor(x) {
    super(x, x);
  }
  getArea() {
    console.log("矩形的面积是:", this.width ** 2);
  }
}

const square = new Square(10);
square.getArea();
square.setHeight(200);
square.getArea();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 迪米特法则,又称最少知道原则

一个接口和一个方法,传入的参数越少越好

✖ 不符合迪米特法则

function getData(obj) {
  return axios.get({
    url: "url",
    params: { obj.id }
  });
}

const obj = { id: 123, name: "xxx" };

getData(obj);
1
2
3
4
5
6
7
8
9
10

✔ 迪米特法则

function getData(id) {
  return axios.get({
    url: "url",
    params: { id }
  });
}

const obj = { id: 123, name: "xxx" };

getData(obj.id);
1
2
3
4
5
6
7
8
9
10

# 接口分离原则

把大接口拆分成小接口

✖ 不符合接口分离原则

interface IAnimal {
  eat: Function;
  fly: Function;
  run: Function;
}

class Dog implements IAnimal {
  eat() {}
  run() {}
  //   fly(){}
}

class Swallow implements IAnimal {
  eat() {}
  fly() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

✔ 接口分离原则

interface IAnimal {
  eat: Function;
}

interface IAnimalSky extends IAnimal {
  fly: Function;
}

interface IAnimalLand extends IAnimal {
  run: Function;
}

class Dog implements IAnimalLand {
  eat() {}
  run() {}
  //   fly(){}
}

class Swallow implements IAnimalSky {
  eat() {}
  fly() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 依赖倒转原则

  1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象
  2. 抽象不应该依赖细节
  3. 细节应该依赖抽象

✖ 依赖倒转原则

// 保存朋友圈
class Web {
  matchRouter() {
    console.log("获取到http请求,转发到对应的service层进行处理");
    //   直接依赖 service 层
    new OrderService().doJob();
    new Service().doJob();
  }
}
class OrderService {
  doJob() {
    console.log("接收到web层的数据,进行数据加工,并转发到对应的db层");
    //   直接依赖 db 层
    new MYSQLDb().insert("data");
    new MONGODb().insert("data");
    new SqlServerDb().insert("data");
  }
}
class ShopService {
  doJob() {
    console.log("接收到web层的数据,进行数据加工,并转发到对应的db层");
    //   直接依赖 db 层
    new MYSQLDb().insert("data");
    new SqlServerDb().insert("data");
  }
}

class MYSQLDb {
  insert(data) {
    console.log("将数据 data 存入 mysql 数据库。");
  }
}
class MONGODb {
  insert(data) {
    console.log("将数据 data 存入 mongodb 数据库。");
  }
}
class SqlServerDb {
  insert(data) {
    console.log("将数据 data 存入 sqlServer 数据库。");
  }
}

document.getElementById("submit").addEventListener("click", () => {
  // 模拟提交
  new Web().matchRouter();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

✔ 依赖倒转原则

// 保存朋友圈
class Web {
  constructor(service) {
    this.service = service;
  }
  matchRouter() {
    console.log("获取到http请求,转发到对应的service层进行处理");
    //   直接依赖 service 层
    this.service.doJob();
    //   发布一个事件,在其他的地方监听这个事件
    // 监听可以有很多,这就实现了一个一对多的关系
  }
}
class Service {
  constructor(db) {
    this.db = db;
  }
  doJob(db) {
    console.log("接收到web层的数据,进行数据加工,并转发到对应的db层");
    //   直接依赖 db 层
    this.db.insert("data");
  }
}

class MySQLDb {
  insert(data) {
    console.log("将数据 data 存入 mysql 数据库。");
  }
}
class MonGoDb {
  insert(data) {
    console.log("将数据 data 存入 mysql 数据库。");
  }
}

document.getElementById("submit").addEventListener("click", () => {
  // 模拟提交
  const db = new MySQLDb();
  const service = new Service(db);
  const web = new Web(service);

  // const db = new MySQLDb()
  // const service = new Service(db);
  // const web = new Web(service)

  const db2 = new MonGoDb();
  const service2 = new Service(db2);
  const web2 = new Web(service2);

  web.matchRouter();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

发布订阅————观察者模式

class Web {
  matchRouter() {
    console.log("匹配到前端路由,触发路由匹配的事件");
    Observer.publish("onRouteChange");
  }
}
class Service {
  doJob() {
    console.log("监听到路由匹配的事件,开始处理业务逻辑");
    console.log("发布业务逻辑处理完成事件");
    Observer.publish("onJobDone");
  }
}
class Db {
  selectData() {
    console.log("查询数据");
  }
}

var Observer = (function() {
  var _message = {};
  return {
    subscribe(type, fn) {
      if (_message[type]) {
        _message[type].push(fn);
      } else {
        _message[type] = [fn];
      }
    },
    publish(type, ...args) {
      if (!_message[type]) {
        return;
      }
      _message[type].forEach(item => {
        item.apply(this, args);
      });
    },
    unsubscribe(type, fn) {
      // fn不传,清楚type上所有的订阅,否则只清除传递的订阅
      if (!_message[type]) {
        return;
      }
      if (fn) {
        _message[type].forEach(function(item, index) {
          item === fn && _message[type].splice(index, 1);
        });
      } else {
        _message[type] = null;
      }
    }
  };
})();

// 注册事件
Observer.subscribe("onRouteChange", new Service().doJob);
Observer.subscribe("onJobDone", new Db().selectData);

new Web().matchRouter();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 设计模式分类

# 创建型

创建型,研究高效的创建对象

  • 单例模式
  • 抽象工厂模式
  • 建造者模式
  • 工厂模式
  • 原型模式

# 结构型

结构型,设计对象和结构之间的关系

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

# 行为型

行为型,设计对象的行为

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

# 设计模式实战

  1. 发布订阅模式
  2. 单例模式
  3. 工厂模式
  4. 代理模式
  5. 命令模式