针对设计问题的通用解决方案
为什么要学设计模式

  • 利于代码复用
  • 利于代码稳定可拓展
  • 利于代码可读性提升

什么时候需要设计模式

  • 优先考虑全局设计
  • 合理权衡使用需求和维护成本

五种设计原则

OCP

开闭原则 (Open Closed Principle)

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 目标: 已有的场景下,对于需要拓展的功能进行开放、拒绝直接的对于系统功能进行修改

// sprint1 - 中秋节活动 吃鸡高亮 + LOL要弹出折扣
// render
if (game === "PUBG") {
// 高亮
} else {
// ……
}

// event
if (game === "LOL") {
// 弹出折扣框
} else {
// 付款
}

// sprint2 - 要对部分游戏置灰 + 付款页面要显示停止发售
// render
if (game === "PUBG") {
// 高亮
} else if (game === "AAA") {
// 灰色
} else {
// ……
}

// event
if (game === "LOL") {
// 弹出折扣框
} else if (game === "AAA") {
// break + 提示停止发售
} else {
// 付款
}

// 重构 => 核心化
// render
gameManager(game).setColor();

// event
gameManager(game).openDialog();

// game库
function gameManager(game) {
return `${game}Manager`;
}

// 导引
const LOLManager = {
setColor() {
// 正常
},

openDialog() {
// 折扣
},
};

const PUBGManager = {
setColor() {
// 高亮
},

openDialog() {
// 付款
},
};

// 重构2
// 默认逻辑抽离
class game {
constructor(name) {
this.name = name;
}

setColor() {
// 设置颜色
}

openDialog() {
// 默认
}
}

class LOL extends game {
openDialog() {
// 折扣
}
}

class PUBG extends game {
setColor() {
// 高亮
}
}

// => yunying@2000@cn@zw => 中国 爪哇 2000年 云隐
// function = 拆分 + 排序 + 翻译 => formatter() + spliter() + sorter() + tranlater()
// => 函数式编程

SRP

单一职责 (Single Responsibility Principle)

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
// SRP - 通过解耦让每一个职责更加的独立
// 目标: 一个功能模块只做一件事情

// game store
class PUBGManager {
openDialog() {
// 弹框
// 计算金额
setPrice();
}
}

const game = new PUBGManager();
game.openDialog(); // 弹框 <=> 计算金额 两个模块存在功能上的耦合

// 重构
// gameManager.js - 业务
class PUBGManager {
constructor(command) {
this.command = command;
}
openDialog(price) {
this.command.setPrice(price);
}
}

// optManager.js - 底层库
class PriceManager {
setPrice(price) {
// 计算金额
}
}

// main.js
const exe = new PriceManager();
const game1 = new PUBGManager(exe);

game1.openDialog(15);

// dialog弹框 => slots(HOC) / decorator / wrapper

DIP

依赖倒置 (Dependency Inversion Principle)

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 目标: 上层应用面向抽象进行coding,而不是面相实现 => 降低需求与实现的耦合

// 需求 - 分享功能
class Store {
constructor() {
this.share = new Share();
}
}

class Share {
shareTo(platform) {
// 分享
}
}

const store = new Store();
store.share.shareTo("wx");

// 评分功能
class Store {
constructor() {
this.share = new Share();
this.rate = new Rate();
}
}

class Share {
shareTo(platform) {
// 分享
}
}

class Rate {
star(stars) {
// 评分
}
}

store.rate.star("wx");

// 目标: 底层不改变 + 动态挂载
// 重构
class Rate {
// 初始化动作
init(store) {
store.rate = this;
}
store(stars) {
// 评分
}
}

class Share {
// 初始化动作
init(store) {
store.share = this;
}
shareTo(platform) {
// 分享
}
}

class Store {
// 维护模块名单
static modules = new Map();

constructor() {
// 遍历名单 + 做初始化挂载
for (let module of Store.module.values()) {
module.init(this);
}
}

// 提供注入功能模块
static inject(module) {
Store.modules.set(module.constructor.name, module);
}
}

// 依次注册所有模块
const rate = new Rate();
Store.inject(rate);

// 初始化商城
// const store = new Store();
store.rate.start(4);

// 依赖收集

ISP

接口隔离 (Interface Segregation Principle)

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
59
60
61
// 目标: 多个专业接口比单个胖接口好用

// 游戏中台 - 快速生产游戏
//
class Game {
constructor(name) {
this.name = name;
}
run() {
// 跑
}
shot() {
// 射击
}
mega() {
// 大招
}
}
// PUBG、LOL
class PUBG extends Game {
constructor() {
// pubg contructor
}
}

class LOL extends Game {
constructor() {
// lol contructor
}
}

pubg = new PUBG("pubg");
pubg.run();
pubg.shot();
pubg.mega();

// 重构 - 用多个接口替代,每个接口服务于一个子模块
class Game {
constructor(name) {
this.name = name;
}
run() {
// 跑
}
}

class FPS {
aim() {}
}

class MOBA {
TP() {}
}

class PUBG extends Game {
shot() {}
}

// => 层级深度 + 划分

// 重构组件划分 + 状态机 / 命名空间

LSP

里氏替换(Liskov Substitution Principle)

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
// 子类可以覆盖父类 => 父类能出现的地方,子类一定能出现

// sprint1
class Game {
start() {
// 开机
}
shutdown() {
// 关机
}
play() {
// 开始游戏
}
}

const game = new Game();
game.play();

// sprint2
class MobileGame extends Game {
tombStore() {}
play() {
// 移动端游戏
}
}

const mobile = new MobileGame();
mobile.play();

// 重构
class Game {
start() {
// 开机
}
shutdown() {
// 关机
}
}

class MobileGame extends Game {
tombStore() {}
play() {
// 移动端游戏
}
}

class PCGame extends Game {
speed() {
// 加速器
}
play() {
// PC游戏
}
}

// 维护核心 => 抽离妥协 增加分层

三类设计模式

创建型

全局只有一个实例,和状态相关

  1. 工厂模式: 批量生产同类型应用来满足频繁使用同一类型素材时
  2. 建造者模式: 需要将元功能模块化解耦,并且需要频繁编排时
  3. 单例模式: 当全局只有一个实例,注重的是一体化和状态统一。可使用代理来实现
1
2
3
4
5
6
7
8
9
10
11
export function singleton(className) {
let ins;
return new Proxy(className, {
construct(target, args) {
if (!ins) {
ins = new target(...args);
}
return ins;
},
});
}

业务场景

  • 工厂: 生产不同类型的按钮
  • 建造者: Header 组件,包含 title、button、breadcum,生产多种不同元素的组合
  • 单例: router、store
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// 功能: 创建元素
// 目的: 规范创建步骤

// 工厂模式
// 隐藏创建过程、暴露共同接口
// 需求:
class Shop {
create(name) {
return new Game(name);
}
}

class Game {
constructor(name) {
this.name = name;
}
init() {}
run() {}
}

const shop = new Shop();
const pubg = new Game("pubg");

pubg.init();
pubg.run();

// 建造者
// 拆分简单模块、独立运行 => 注重过程与搭配
// 需求:
// 优惠套餐,商品 + 皮肤 进行打折售卖
class Product {
constructor(name) {
this.name = name;
}
init() {
return "game: " + this.name;
}
}

class Skin {
constructor(name) {
this.name = name;
}
init() {
return "skin: " + this.name;
}
}

class Shop {
constructor() {
this.package = "";
}
create(name) {
this.package = new PackageBuilder(name);
}
getGamePackage() {
this.package.getPackage();
}
}

class PackageBuilder {
constructor(name) {
this.game = new Product(name);
this.skin = new Skin(name);
}
getPackage() {
return this.game.init() + this.skin.init();
}
}

// 单例模式
// 全局只有一个实例
class PlayStation {
constructor() {
this.state = "off";
}
play() {
if (this.state === "on") {
return;
}
this.state = "on";
}
shutdown() {
if (this.state === "off") {
return;
}
this.state = "off";
}
}

// main.js
PlayStation.instance = undefined;
PlayStation.getInstance = function () {
return (function () {
if (!PlayStation.instance) {
PlayStation.instance = new PlayStation();
}
return PlayStation.instance;
})();
};

const ps = PlayStation.getInstance();
ps.play();
ps.shutdown();

结构型

不影响用户体验

  1. 适配器模式: 中间参数转换,兼容\适配已有方案时
  2. 装饰器模式: 在已有方案中批量提供\提升相应功能时
  3. 代理模式: 把调用方和被调用方分离,不直接产生调用关系时

业务场景

  • 适配器: 需求中添加适配器做数据结构的转换
  • 装饰器: title、button、breadcum 三个组件同时都具有发生改变触发页面刷新的功能
  • 代理: ul 标签监听 li 标签,代理到 ul 上监听
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 功能:  优化结构实现

// 适配器模式
// 适配独立模块,保证模块间的独立解耦以及连接兼容

class HKDevice {
getPlug() {
return "港行插头";
}
}

class Target {
constructor() {
this.plug = new HKDevice();
}
getPlug() {
return this.plug.getPlug() + "+ 插头转接器";
}
}

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

// 装饰器模式
// 动态将责任附加在对象上
class Device {
create() {
console.log("PlayStation4");
}
}
class Phone {
create() {
console.log("iphone13");
}
}

class Decorator {
constructor(device) {
this.device = device;
}
create() {
this.device.create();
this.update(this.device);
}
update(device) {
console.log(device + "pro");
}
}

const device = new Device();
device.create();

const newDevice = new Decorator(device);
newDevice.create();

// 代理模式
// 使用代理人来替代原始对象
// 游戏防沉迷
class Game {
play() {
return "playing";
}
}

class Player {
constructor(age) {
this.age = age;
}
}

class GameProxy {
constructor(player) {
this.player = player;
}
play() {
return this.player.age < 16 ? "too young to play" : new Game().play();
}
}

行为型

节点互斥、前后链式相连

  1. 命令模式: 具有发出方 + 媒介 + 接收方 的完整流程时
  2. 模板模式: 对于基础说明书,进行的一次实际业务结合操作时
  3. 观察者模式: 通过观察者,可以让被观察者实时接收消息,平且触发一定的业务操作时
  4. 职责链模式: 独立指责单元链式执行,逐步操作流程时

业务场景

  • 命令: 组件 A 通知组件 B,媒介抽象为数据处理中间层
  • 模板: echarts、canvas、config…
  • 观察者: 输入框变化,下拉框实时更新选项
  • 职责链: 提交表单,链式调用 validate,依次执行
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// 不同对象之间行为逻辑的抽象化

// 命令模式
// 请求以命令的形式包裹在对象中,并传给调用对象

// 角色命令接受者
class Receiver {
execute() {
console.log("开始奔跑");
}
}

class Operator {
constructor(command) {
this.command = command;
}
run() {
this.command.execute();
}
}

// 指令器
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.execute();
}
}

const soldier = new Receiver();
const order = new Command(soldier);
const player = new Operator(order);
player.run();

// 模板模式
// 模板中,定义好个方法的步骤,方法本身更关注自己的事情
class Device {
constructor(executePipeLine) {
// executePipeLine
}
powerOn() {
console.log("开机");
}
login() {
console.log("登录账号");
}
clickStart() {
console.log("开始游戏");
}
enterGame() {
console.log("进入战场");
}
play() {
this.powerOn();
this.login();
this.clickStart();
this.enterGame();
}
}

// 观察者模式
// 当一个属性发生改变时,观察者会连续引发所有的相关类型的状态改变

class MediaCenter {
constructor() {
this.state = "";
this.observers = [];
}
attach(observer) {
this.observers.push(observer);
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
this.notifyAllobservers();
}
notifyAllobservers() {
this.observers.forEach((ob) => {
ob.update();
});
}
}

class Observer {
constructor(name, center) {
this.name = name;
this.center = center;
this.center.attach(this);
}
update() {
return {
name: this.name,
state: this.center.getState(),
};
}
}
const center = new MediaCenter();
const ps = new Observer("ps", center);

center.setState("on");

// 职责链
// 1. 链式调用 2. 职责独立 3. 顺序执行

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 dad = new Action("爸");
const mom = new Action("妈");
const wife = new Action("夫人");

dad.setNextAction(mom);
mom.setNextAction(wife);

dad.handle();

// generator()
// .next()