JS 设计模式
面试敲门砖、进阶垫脚石、设计有模式、代码更合理
- 第1章 基础知识
- 第2章 面向对象
- 第3章 设计原则
- 第4章 工厂模式
- 第5章 单例模式
- 第6章 适配器模式
- 第7章 装饰器模式
- 第8章 代理模式
- 第9章 外观模式
- 第10章 观察者模式
- 第11章 迭代器模式
- 第12章 状态模式
- 第13章 其他设计模式
- 第 14 章 推荐
第1章 基础知识
1-1 Node.js 基础知识
- Node.js介绍
- Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。
- Node.js安装
- Node.js 入门
1-2 TypeScript 基础知识
- TS 入门
第2章 面向对象
2-2 什么是面向对象
- 1. 什么是面向对象编程?
- 面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。
- 2. 什么是面向对象编程语言?
- 支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言
- 3. 如何判定一个编程语言是否是面向对象编程语言?
- 严格:支持类、对象、四大特性
- 宽泛:类、对象
- 4. 面向对象编程和面向对象编程语言之间有何关系?
- 面向对象编程不一定要用面向对象语言,使用面向对象语言写出的代码也不一定是面向对象语言的
- 5. 什么是面向对象分析和面向对象设计?
- 面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做。
- 产出是类图
2-3 面向对象-封装
- 语法关键字:private、protected、public
- 意义:
- 保护数据不被随意修改,提高代码的可维护性
- 仅暴露有限的必要接口,提高类的易用性。
2-4 面向对象-抽象
- 讲如何隐藏方法的具体实现
- 意义:
- 提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围
- 处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。
2-5 面向对象-继承
- 表示类之间的 is-a 关系
- 单继承和多继承
- 意义:解决代码复用的问题。
2-6 面向对象-多态
- 多态是指子类可以替换父类
- 意义:提高代码的扩展性和复用性
2-7 面向对象-总结
- 面向对象编程有哪些优势?
- 更能应对这种复杂类型的程序开发
- 更加丰富的特性(封装、抽象、继承、多态)
- 易扩展、易复用、易维护。
- 更加人性化、更加高级、更加智能。
2-8 UML图-介绍
UML 中分9种图:
静态模型图: 描述系统的静态结构
类图:显示系统中的类, 接口以及它们之间的关系.
对象图:类图的一个实例
组件图:各组件之间的关系
部署图:显示软件系统不同的组件将在何处物理地运行,以及它们将如何彼此通信
动态模型图: 描述系统的行为
用例图:从客户的角度来描述系统功能
活动图(流程图):描述系统的活动, 判定点和分支等.
时序图:描述对象之间消息的传递时间顺序
协作图:表达对象间的交互过程及对象间的关联关系
状态图:通过建立对象的生存周期模型来描述对象随时间变化的动态行为
2-9 UML类图
- 类图
- 数据类型
+
表示 public,–
表示 private#
表示 protected~
表示 package
- 数据类型
- 接口的表示法(实线空心箭头)
类图 中主要包括 4 种关系:
- 泛化关系(继承关系):实线空心箭头
实现关系(类与接口之间的实现关系):实线空心箭头|虚线空心箭头
依赖关系:虚线的箭头
关联关系
聚合关系:空心菱形的实心线,菱形指向整体
- 是整体与部分的关系,且部分可以离开整体而单独存在
组成关系:带实心菱形的实线,菱形指向整体
- 是整体与部分的关系,但部分不能离开整体而单独存在
第3章 设计原则
3-1 设计原则-介绍
- 即按照哪一种思路或者标准来实现功能
- 功能相同,可以有不同的设计方案来实现
- 伴随着需求的增加,设计的作用才能体现出来
3-2 设计原则-何为设计
设计准则:
- 1 小既是美
- 2 让每个程序只做好一件事
- 3 快速建立原型
- 4 舍弃高效率而取可移植性
- 5采用纯文本来存储数据
- 6 充分利用软件的杠杆效应(软件复用)
- 7 使用shell脚本来提高杠杆效应和可移植性
- 8 避免强制性的用户界面
- 9 让每一个程序都称为过滤器
小准则:
- 允许用户定制环境
- 尽量使操作系统内核小而轻量化
- 使用小写字母并尽量简短
- 沉默是金
- 各部分之和大于整体
- 寻求 90% 的解决方案
3-3 设计原则-5大原则
S O L I D 五大设计原则
- S - 单一责任原则
- O - 开放封闭原则
- L - 里氏替换原则
- I - 接口独立原则
- D - 依赖倒置原则
单一责任原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分,每个部分保持独立
开放封闭原则
- 对扩展开发,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
- 这个是软件设计的终极目标
里氏替换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
- JS中使用较少(弱类型&继承使用较少)
接口独立原则
- 保持接口的单一独立,避免出现 “胖接口”
- JS中没有接口(typescript例外),使用较少
- 类似于单一职责原则,这里更关注接口
依赖倒置原则
- 面向接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体类的实现
- JS中使用较少
设计原则总结
- S O 体现较多,详细介绍
- LID 体现较少,但是要了解其用意
用Promise来说明 S-O
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)
})
- 单一职责原则:每个 then 中的逻辑只做好一件事
- 开放封闭原则:如果新增需求,扩展then
- 对扩展开发,对修改封闭
3-4 用promise演示
就是3-3的代码
3-5 设计模式简介
从设计到模式
体会什么是设计?设计是设计,模式是模式,两者是分离的。
该如何学习设计模式?
- 明白每个设计的道理和用意
- 通过经典应用体会它的真正使用场景
- 自己编码时多思考,尽量模仿
3-6 23种设计模式介绍
其实设计模式大致分为三种类型:
- 创建型
- 组合型
- 行为型
这23种设计模式分别分散在这三种类型中。
创建型
- 工厂模式(工厂方法模式、抽象工厂模式、建造者模式)
- 工厂模式是讲怎么面向对象、怎么创建对象、怎么生成
- 单例模式
- 单例模式是讲如果这个系统中只有一个指定对象,那么出现第二个时,该怎么办
- 原型模式
- 原型模式是讲如何通过一个现有的对象,用拷贝的方式来生成另一个新的对象
结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
行为型
- 策略模式
- 模板方法模式
- ★观察者模式★
- 迭代器模式
- 职责链模式
- 命令模式
- 备忘录模式
- ★状态模式★
- 访问者模式
- 中介模式
- 解释器模式
如何讲解设计模式
- 介绍和举例(生活中易理解的示例)
- 画UML类图写demo代码
- 结合经典应用场景,讲解该设计模式如何被使用
3-7 面试真题1
- 打车时,可以打专车或者快车。任何车都有车牌号和名称
- 解析:需设计公共父类(车牌号和名称),父类下又有两子类(专车和快车))
- 不同车价格不同,快车每公里1元,专车每公里2元
- 解析:子类里有不同的价格
- 行程开始时,显示车辆信息
- 行车和车有关系,但和专车还是快车没关系。所以我们需要依赖抽象编程,所以行程只和车有关系,不和具体哪种车有关,也就是说无论什么车都有行车信息
- 所以我们需要再建一个"行程"的类,这个类引用车的某个属性,我们可以通过这个属性得到车的信息(车牌号、名称、单价)
- 行程结束时,显示打车金额(假定行程就5公里)
- “金额”属于行程。买了一万辆车丢着是没有行程金额的
UML类图
class Car {
constructor(number, name) {
this.number = number
this.name = name
}
}
class Kuaiche extends Car {
constructor(number, name) {
super(number, name)
this.Price = 1
}
}
class Zhuanche extends Car {
constructor(number, name) {
super(number, name)
this.Price = 2
}
}
class Trip {
constructor(car) {
this.car = car
}
start() {
console.log(`行程开始,名称:${this.car.name},车牌号:${this.car.Price}`)
}
end() {
console.log(`行程结束,价格:${this.car.Price * 5}`)
}
}
let car = new Kuaiche('101', '捷达')
let trip = new Trip(car)
trip.start()
trip.end()
3-8 面试真题2
- 某停车场,分3层,每层100车位
- 解析:三个类,分别是停车场、每层、车位,三个class
- 每个车位都能监控到车辆的驶入和离开
- 解析:我们要给车位这个类定义一个方法或者属性来监控车辆驶入和离开,这个监控的方法要改变车位这个类的一个状态,车位空不空
- 车辆进入前,显示每层的空余车位数量
- 解析:车辆进入前肯定面对的是停车场这个类,所以这个信息要在停车场这个类中释放出来,所以我们加一个方法,动态计算显示每一层(每一层都是一个类的实例)空车位,所以层这个类里还得加显示空车位的方法,最终由停车场这个类累加后显示
- 车辆进入时,摄像头可以识别车牌号和时间
- 解析:还得加摄像头的class,这个class有方法能识别出车牌号和记录驶入时间,也就是说摄像头这个类,输入的是车的实例,输出车牌号和时间,这个车牌号和时间要让停车场那个类里去存,所以停车场这个类还得加车辆列表的属性
- 车辆出来时,出口显示器显示车牌号和停车时长
- 解析:还得加显示器的类,通过显示器拿到车牌号和记录的驶入时间,然后用当前时间减去这个事件就拿到了停车时长
// 车
class Car {
constructor(num) {
this.num = num
}
}
// 入口摄像头
class Camera {
shot(car) {
return {
num: car.num,
inTime: Date.now()
}
}
}
// 出口显示器
class Screen {
show(car, inTime) {
console.log('车牌号', car.num)
console.log('停车时间', Date.now() - inTime)
}
}
// 停车场
class Park {
constructor(floors) {
this.floors = floors || []
this.camera = new Camera()
this.screen = new Screen()
this.carList = {}
}
in(car) {
// 获取摄像头的信息:号码 时间
const info = this.camera.shot(car)
// 停到某个车位
const i = parseInt(Math.random() * 100 % 100)
const place = this.floors[0].places[i]
place.in()
info.place = place
// 记录信息
this.carList[car.num] = info
}
out(car) {
// 获取信息
const info = this.carList[car.num]
const place = info.place
place.out()
// 显示时间
this.screen.show(car, info.inTime)
// 删除信息存储
delete this.carList[car.num]
}
emptyNum() {
return this.floors.map(floor => {
return `${floor.index} 层还有 ${floor.emptyPlaceNum()} 个车位`
}).join('\n')
}
}
// 层
class Floor {
constructor(index, places) {
this.index = index
this.places = places || []
}
emptyPlaceNum() {
let num = 0
this.places.forEach(p => {
if (p.empty) {
num = num + 1
}
})
return num
}
}
// 车位
class Place {
constructor() {
this.empty = true
}
in() {
this.empty = false
}
out() {
this.empty = true
}
}
// 测试代码------------------------------
// 初始化停车场
const floors = []
for (let i = 0; i < 3; i++) {
const places = []
for (let j = 0; j < 100; j++) {
places[j] = new Place()
}
floors[i] = new Floor(i + 1, places)
}
const park = new Park(floors)
// 初始化车辆
const car1 = new Car('A1')
const car2 = new Car('A2')
const car3 = new Car('A3')
console.log('第一辆车进入')
console.log(park.emptyNum())
park.in(car1)
console.log('第二辆车进入')
console.log(park.emptyNum())
park.in(car2)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车进入')
console.log(park.emptyNum())
park.in(car3)
console.log('第三辆车离开')
park.out(car3)
3-9 总结
第4章 工厂模式
原理
- 将
new
操作单独封装 - 遇到
new
时,就要考虑是否该使用工厂模式
示例
- 你去购买汉堡,直接点餐、取餐,不会自己亲手做
- 商店要 “封装” 做汉堡的工作,做好直接给顾客
/**
* 工厂模式示例,逻辑如图:
*
* -------------------------- ----------------|
* | Creator | | Product |
* |------------------------| |---------------|
* | | | + name:String |
* |------------------------| -> |---------------|
* | + create(name):Product | | + init() |
* -------------------------- | + fn1() |
* | + fn2() |
* ----------------|
*/
class Product {
constructor(name) {
this.name = name;
}
init() {
console.log("init", this.name);
}
fn1() {
console.log("fn1", this.name);
}
fn2() {
console.log("fn2", this.name);
}
}
class Creator {
create(name) {
return new Product(name);
}
}
// 测试
const creator = new Creator();
const p1 = creator.create("test1");
const p2 = creator.create("test2");
p1.init();
p2.init();
p1.fn1();
p2.fn1();
p1.fn2();
p2.fn2();
场景
jQuery - $('div')
React.createElement
vue异步组件
React.createElement
React.createElement使用工厂模式的好处:如果我们不用 createElement 封装 new VNode(tag,attrs, children)
,在对生成VNode示例时我们还是让用户去验证各个属性参数,显示不合理,而且用了工厂模式后用户根本不关系内部构造函数怎么变化。
vue异步组件
Vue异步加载组件完成后创建组件的模式
使用工厂模式把工厂内部构造函数与用户隔离
设计原则验证
- 构造函数和创建者分离
- 符合开放封闭原则
第5章 单例模式
- 系统中仅被唯一使用的
- 一个类只有一个实例
/**
* 单例模式
*/
class SingleObject {
login() {
console.log("login...");
}
}
// 创建一个静态自执行的方法
SingleObject.getInstance = (function() {
let instance;
return function() {
if (!instance) {
instance = new SingleObject();
}
return instance;
}
})()
// 测试
let obj1 = SingleObject.getInstance();
obj1.login();
let obj2 = SingleObject.getInstance();
obj2.login();
console.log(obj1 === obj2);
示例
- 登录框
class LoginForm() {
constructor() {
this.state = 'hide'
}
hide() {
if(this.state === 'hide'){
console.log('已经隐藏')
return
}
this.state == 'hide'
consoel.log('隐藏成功')
}
show() {
if(this.state === 'show'){
console.log('已經顯示')
return
}
this.state === 'show'
console.log('顯示成功')
}
}
LoginForm.instance = (function(){
let instance
return function(){
if(!instance){
instance = new LoginForm()
}
return instance
}
})()
let login1 = LoginForm.instance()
login1.hide()
let login2 = LoginForm.instance()
login2.hide()
- 购物车
- vuex和redux中的store
jQuery
永远只有一个
设计 原则 验证
- 符合单一职责原则,只实例化唯一的对象
- 没法具体开放封闭原则,但是绝对不违反开放封闭原则
第6章 适配器模式
- 就接口格式和使用者不兼容
- 中间加一个适配器接口
/**
* 适配器模式
*/
class Adapter {
specificRequest() {
return "旧的接口内容"
}
}
class Target {
constructor() {
this.adapter = new Adapter();
}
request() {
let info = this.adapter.specificRequest();
return `${info} - 处理... - 新的接口内容`;
}
}
// 测试
let target = new Target();
const r = target.request();
console.log(r);
场景
- 封装旧接口
- Vue的computed
设计原则验证
- 将就借口和使用者进行分离
- 符合开放封闭原则
第7章 装饰器模式
- 为对象添加新功能
- 不改变其原有的结构和功能
- 将现有对象和装饰器进行分离,两者独立存在
/**
* 装饰器模式
*/
class Circle {
draw() {
console.log("画一个圆");
}
}
class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
this.circle.draw();
this.setRedBorder(this.circle);
}
setRedBorder(circle) {
console.log("设置红色边框");
}
}
// 测试
let c = new Circle();
c.draw();
let d = new Decorator(c);
d.draw();
第8章 代理模式
8-1 代理模式-介绍和演示
- 使用者无权访问目标对象
- 中间加代理,通过代理做授权和控制
- 代理类与目标类分离,隔离开目标类和使用者
8-2 代理模式-场景1(事件代理和jq的proxy)
8-3 代理模式-场景2(明星经纪人)
/**
* 代理模式
*/
class ReadImg {
constructor(filename) {
this.filename = filename;
this.loadFromDisk();
}
loadFromDisk() {
console.log("从硬盘加载数据" + this.filename);
}
display() {
console.log("显示数据" + this.filename);
}
}
class ProxyImg {
constructor(filename) {
this.realImg = new ReadImg(filename);
}
display() {
this.realImg.display();
}
}
// test
let proxyImg = new ProxyImg("1.png");
proxyImg.display();
// =================================
/**
* 使用ES6语法的Proxy类演示 代理模式的示例,明星 - 经纪人
*/
let star = {
name: "张xx",
age : 25,
phone: "138123456789"
}
let agent = new Proxy(star, {
get: function(target, key) {
if (key === "phone") {
return "agent phone: 13555555555";
}
else if (key === "price") {
return 150000;
}
return target[key];
},
set: function(target, key, val) {
if (key === "customPrice") {
if (val < 10000) {
throw new Error("价格太低");
} else {
target[key] = val;
return true;
}
}
}
})
// test
console.log(agent.name);
console.log(agent.phone);
console.log(agent.age);
console.log(agent.price);
agent.customPrice = 120000; // OK
console.log(agent.customPrice);
agent.customPrice = 1000; // Error
console.log(agent.customPrice);
8-4 代理&适配器&装饰模式对比
- 代理模式VS适配器模式
- 适配器模式:提供一个不同的接口(如不同版本的插头)
- 代理模式:提供一模一样的接口
- 代理模式VS装饰器模式
- 装饰器模式:扩展功能,原有功能不变且可直接使用
- 代理模式:显示原有的功能,但是经过限制或者阉割之后的
第9章 外观模式
9-1 外观模式
- 为子系统中的一组接口提供了一个高层接口
- 使用者使用高层接口
- 不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用
第10章 观察者模式
10-1 观察者模式-介绍和演示
- 发布 订阅
- 一对多
- 主题和观察者分离,不是主动触发而是被动监听,两者解耦
10-2 观察者模式-场景1jquery
10-3 观察者模式-场景2NodeJs自定义事件
/**
* 观察者模式,使用nodejs的events模块的示例
*/
const EventEmitter = require("events").EventEmitter;
// =========EventEmitter的基础用法=============
const emitter1 = new EventEmitter();
// 监听some事件
emitter1.on("some", info => {
console.log("fn1", info);
})
// 监听some事件
emitter1.on("some", info => {
console.log("fn2", info);
})
// 触发some事件
emitter1.emit("some", "xxxx");
// =============================================
// 下面使用继承来实现EventEmitter
class Dog extends EventEmitter {
constructor(name) {
super();
this.name = name;
}
}
let dog = new Dog("dog");
dog.on("bark", function() {
console.log(this.name, " barked-1");
})
dog.on("bark", function() {
console.log(this.name, " barked-2");
})
setInterval(() => {
dog.emit("bark")
}, 1000);
10-4 观察者模式-其它场景
/**
* 观察者模式
*/
// 主题,保存状态,状态变化之后触发所有观察者对象
class Subject {
constructor() {
this.state = 0;
this.observers = [];
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
this.notifyAllObservers();
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update();
})
}
attach(observer) {
this.observers.push(observer);
}
}
// 观察者
class Observer {
constructor(name, subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this);
}
update() {
console.log(`${this.name} update! state is: ${this.subject.state}`);
}
}
// 测试
let s = new Subject();
let o1 = new Observer("o1", s);
let o2 = new Observer("o2", s);
let o3 = new Observer("o3", s);
let o4 = new Observer("o4", s);
s.setState(1);
s.setState(2);
第11章 迭代器模式
11-1 迭代器模式-介绍
- 顺序访问一个集合
- 使用者无需知道集合的内部结构(封装)
- 迭代器对象与目标对象分离
- 迭代器将使用者与目标对象隔离开
11-2 迭代器模式-演示
11-3 迭代器模式-场景1(ES6 Iterator)
11-4 迭代器模式-场景2
11-5 迭代器模式-代码演示和总结
/**
* 迭代器模式
*/
class Iterator {
constructor(container) {
this.list = container.list;
this.index = 0;
}
next() {
if (this.hasNext()) {
return this.list[this.index++];
}
return null;
}
hasNext() {
if (this.index >= this.list.length) {
return false;
}
return true;
}
}
class Container {
constructor(list) {
this.list = list;
}
// 生成遍历器
getIterator() {
return new Iterator(this);
}
}
// 测试
const arr = [1, 2, 3, 4, 5];
let container = new Container(arr);
let it = container.getIterator();
while(it.hasNext()) {
console.log(it.next());
}
// ============= 使用ES6的迭代器生成 =============
function each(data) {
// 生成遍历器
let it = data[Symbol.iterator]();
let item;
do {
// 遍历器生成可迭代内容,包含value和done属性,
// 其中done属性代替自定义的hasNext()方法,
// false表示还有数据,true则表示已经迭代完成
item = it.next();
if (!item.done) {
console.log(item.value);
}
} while (!item.done);
}
// ES6的Iterator已经封装在了语法 for...of 中,直接使用即可
function each2(data) {
for (const item of data) {
console.log(item);
}
}
// 测试
const arr2 = [10, 20, 30, 40, 50, 60];
let m = new Map();
m.set("a", 100);
m.set("b", 200);
m.set("c", 300);
each(arr2);
each(m);
each2(arr2);
each2(m);
第12章 状态模式
12-1 状态模式-介绍和演示
- 允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类
/**
* 状态模式
*/
// 模拟红绿灯状态
class State {
constructor(color) {
this.color = color;
}
handle(context) {
console.log(`切换到 ${this.color} `);
context.setState(this);
}
}
// 主体
class Context {
constructor() {
this.state = null;
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
}
}
// 测试
let context = new Context();
let green = new State("绿灯");
let yellow = new State("黄灯");
let red = new State("红灯");
// 绿灯亮
green.handle(context);
console.log(context.getState());
// 黄灯亮
yellow.handle(context);
console.log(context.getState());
// 红灯亮
red.handle(context);
console.log(context.getState());
12-2 状态模式-场景1(有限状态机)
12-3 状态模式-场景2(写一个promise)
import * as fs from "fs";
import * as StateMachine from 'javascript-state-machine';
import * as request from 'request';
// promise state: resolve(pending => fullfilled), reject(pending => rejected)
const fsm = new StateMachine({
init: 'pending',
transitions: [
{
name: 'resolve',
form: 'pending',
to: 'fullfilled'
}, {
name: 'reject',
from: 'pending',
to: 'rejected'
}
],
methods: {
onResolve: function(state, data, data1) {
// sate 当前状态机实例;data fsm.resolve(xxx) 传递的参数
// console.log(state, data)
data.succFnList.forEach(fn => fn(data1));
},
onReject: function(state, data) {
data.failFnList.forEach(fn => fn());
}
},
});
class MyPromise {
succFnList: any[];
failFnList: any[];
constructor(fn) {
this.succFnList = [];
this.failFnList = [];
fn((data) => {
// resolve 函数
fsm.resolve(this, data);
},
() => {
// reject 函数
fsm.reject(this);
});
}
then(succFn, failFn) {
this.succFnList.push(succFn);
this.failFnList.push(failFn);
}
}
function downloadImg(src) {
const promise = new MyPromise(function(resolve, reject) {
request(src, function(error, response, body) {
if (error) {
reject();
}
resolve(body);
})
});
return promise;
}
const imgSrc = 'https://www.npmjs.com/package/javascript-state-machine';
const imgPromise = downloadImg(imgSrc);
imgPromise.then(function(data) {
console.log(fsm.state)
fs.writeFileSync('./test.html', data)
}, function(error) {
console.log(error);
});
第13章 其他设计模式
13-1 其他设计模式概述
13-2 原型模式
- 原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
/**
* 原型模式
* prototype可以理解为ES6中class的一种底层原理,但是class是实现面向对象的基础,并不是服务于某个模式
*/
// 创建一个原型
let prototype = {
getName: function() {
return this.first + " " + this.last;
},
say: function() {
console.log("Hello!");
}
}
// 基于原型创建x
let x = Object.create(prototype);
x.first = "A";
x.last = "B";
console.log(x.getName());
x.say();
// 基于原型创建y
let y = Object.create(prototype);
y.first = "C";
y.last = "D";
console.log(y.getName());
y.say();
13-3 桥接模式
- 桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化。
/**
* 桥接模式
*/
class Color {
constructor(name) {
this.name = name;
}
}
class Shape {
constructor(name, color) {
this.name = name;
this.color = color;
}
draw() {
console.log(`使用${this.color.name}颜色画了一个${this.name}`);
}
}
// 测试
let red = new Color("red");
let yellow = new Color("yellow");
let circle = new Shape("circle", red);
circle.draw();
let triangle = new Shape("triangle", yellow);
triangle.draw();
13-4 组合模式
- 将对象组合成树形结构,以表示“整体-部分”的层次结构。
- 通过对象的多态表现,使得用户对单个对象和组合对象的使用具有一致性。
class TrainOrder {
create () {
console.log('创建火车票订单')
}
}
class HotelOrder {
create () {
console.log('创建酒店订单')
}
}
class TotalOrder {
constructor () {
this.orderList = []
}
addOrder (order) {
this.orderList.push(order)
return this
}
create () {
this.orderList.forEach(item => {
item.create()
})
return this
}
}
// 可以在购票网站买车票同时也订房间
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()
13-5 享元模式
- 运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式
let examCarNum = 0 // 驾考车总数
/* 驾考车对象 */
class ExamCar {
constructor(carType) {
examCarNum++
this.carId = examCarNum
this.carType = carType ? '手动档' : '自动档'
this.usingState = false // 是否正在使用
}
/* 在本车上考试 */
examine(candidateId) {
return new Promise((resolve => {
this.usingState = true
console.log(`考生- ${ candidateId } 开始在${ this.carType }驾考车- ${ this.carId } 上考试`)
setTimeout(() => {
this.usingState = false
console.log(`%c考生- ${ candidateId } 在${ this.carType }驾考车- ${ this.carId } 上考试完毕`, 'color:#f40')
resolve() // 0~2秒后考试完毕
}, Math.random() * 2000)
}))
}
}
/* 手动档汽车对象池 */
ManualExamCarPool = {
_pool: [], // 驾考车对象池
_candidateQueue: [], // 考生队列
/* 注册考生 ID 列表 */
registCandidates(candidateList) {
candidateList.forEach(candidateId => this.registCandidate(candidateId))
},
/* 注册手动档考生 */
registCandidate(candidateId) {
const examCar = this.getManualExamCar() // 找一个未被占用的手动档驾考车
if (examCar) {
examCar.examine(candidateId) // 开始考试,考完了让队列中的下一个考生开始考试
.then(() => {
const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()
nextCandidateId && this.registCandidate(nextCandidateId)
})
} else this._candidateQueue.push(candidateId)
},
/* 注册手动档车 */
initManualExamCar(manualExamCarNum) {
for (let i = 1; i <= manualExamCarNum; i++) {
this._pool.push(new ExamCar(true))
}
},
/* 获取状态为未被占用的手动档车 */
getManualExamCar() {
return this._pool.find(car => !car.usingState)
}
}
ManualExamCarPool.initManualExamCar(3) // 一共有3个驾考车
ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 10个考生来考试
13-6 策略模式
- 定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换
/**
* 策略模式
*/
// 普通情况下,没有使用策略模式
class User {
constructor(type) {
this.type = type;
}
buy() {
if (this.type === "ordinary") {
console.log("普通用户购买");
} else if (this.type === "member") {
console.log("会员用户购买");
} else if (this.type === "vip") {
console.log("高级会员购买");
}
}
}
// 使用
let u1 = new User("ordinary");
u1.buy();
let u2 = new User("member");
u2.buy();
let u3 = new User("vip");
u3.buy();
// ================ 使用策略模式进行调整 ===================
class OrdinaryUser {
buy() {
console.log("普通用户购买");
}
}
class MemberUser {
buy() {
console.log("会员用户购买");
}
}
class VipUser {
buy() {
console.log("高级会员用户购买");
}
}
// 测试
let ou = new OrdinaryUser();
ou.buy();
let mu = new MemberUser();
mu.buy();
let vu = new VipUser();
vu.buy();
13-7 模板方法模式和职责连模式
/**
* 职责链模式
*/
class Action {
constructor(name) {
this.name = name;
this.nextAction = null;
}
setNextAction(action) {
this.nextAction = action;
}
handle() {
console.log(`${this.name} 执行了操作`);
if (this.nextAction) {
this.nextAction.handle();
}
}
}
// 测试
let a1 = new Action("组长");
let a2 = new Action("经理");
let a3 = new Action("总监");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();
13-8 命令模式
- 将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
/**
* 命令模式
*/
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;
}
invoke() {
console.log("开始");
this.command.cmd();
}
}
// 测试
let soldier = new Receiver();
let trumpeter = new Command(soldier);
let general = new Invoker(trumpeter);
general.invoke();
13-9 备忘录模式
- 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
/**
* 备忘录模式
*/
// 备忘类
class Memento {
constructor(content) {
this.content = content;
}
getContent() {
return this.content;
}
}
// 备忘列表
class CareTaker {
constructor() {
this.list = [];
}
add(memento) {
this.list.push(memento);
}
get(index) {
return this.list[index];
}
}
// 编辑器
class Editor {
constructor() {
this.content = null;
}
setContent(content) {
this.content = content;
}
getContent() {
return this.content;
}
saveContentToMemento() {
return new Memento(this.content);
}
getContentFromMemento(memento) {
this.content = memento.getContent();
}
}
// 测试
let editor = new Editor();
let careTaker = new CareTaker();
editor.setContent("111");
editor.setContent("222");
careTaker.add(editor.saveContentToMemento()); // 备份
editor.setContent("333");
careTaker.add(editor.saveContentToMemento()); // 备份
editor.setContent("444");
console.log(editor.getContent());
editor.getContentFromMemento(careTaker.get(1)); // 撤销
console.log(editor.getContent());
editor.getContentFromMemento(careTaker.get(0)); // 撤销
console.log(editor.getContent());
13-10 中介者模式
- 解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的 相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者
模式使网状的多对多关系变成了相对简单的一对多关系(类似于观察者模式,但是单向的,由中介者统一管理。)
/**
* 中介者模式
*/
class A {
constructor() {
this.number = 0;
}
setNumber(num, m) {
this.number = num;
if (m) {
m.setB();
}
}
}
class B {
constructor() {
this.number = 0;
}
setNumber(num, m) {
this.number = num;
if (m) {
m.setA();
}
}
}
class Mediator {
constructor(a, b) {
this.a = a;
this.b = b;
}
setA() {
let number = this.b.number;
this.a.setNumber(number / 100);
}
setB() {
let number = this.a.number;
this.b.setNumber(number * 100);
}
}
// 测试
let a = new A();
let b = new B();
let m = new Mediator(a, b);
a.setNumber(100, m);
console.log(a.number, b.number);
b.setNumber(100, m);
console.log(a.number, b.number);
13-11 访问者模式和解释器模式
- 访问者模式: 给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
class Context {
constructor() {
this._list = []; // 存放 终结符表达式
this._sum = 0; // 存放 非终结符表达式(运算结果)
}
get sum() {
return this._sum;
}
set sum(newValue) {
this._sum = newValue;
}
add(expression) {
this._list.push(expression);
}
get list() {
return [...this._list];
}
}
class PlusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = ++context.sum;
}
}
class MinusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = --context.sum;
}
}
/** 以下是测试代码 **/
const context = new Context();
// 依次添加: 加法 | 加法 | 减法 表达式
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new MinusExpression());
// 依次执行: 加法 | 加法 | 减法 表达式
context.list.forEach(expression => expression.interpret(context));
console.log(context.sum);