JS 设计模式
发布于 7 天前 作者 18820227745 2738 次浏览 来自 分享

JS 设计模式

面试敲门砖、进阶垫脚石、设计有模式、代码更合理

源码

[toc]

第1章 基础知识

1-1 Node.js 基础知识

1-2 TypeScript 基础知识

第2章 面向对象

2-2 什么是面向对象

    1. 什么是面向对象编程?
    • 面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。
    1. 什么是面向对象编程语言?
    • 支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言
    1. 如何判定一个编程语言是否是面向对象编程语言?
    • 严格:支持类、对象、四大特性
    • 宽泛:类、对象
    1. 面向对象编程和面向对象编程语言之间有何关系?
    • 面向对象编程不一定要用面向对象语言,使用面向对象语言写出的代码也不一定是面向对象语言的
    1. 什么是面向对象分析和面向对象设计?
    • 面向对象分析就是要搞清楚做什么,面向对象设计就是要搞清楚怎么做。
    • 产出是类图

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% 的解决方案

源于:《UNIX/LINUX 设计思想》

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类图

uml01

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有方法能识别出车牌号和记录驶入时间,也就是说摄像头这个类,输入的是车的实例,输出车牌号和时间,这个车牌号和时间要让停车场那个类里去存,所以停车场这个类还得加车辆列表的属性
  • 车辆出来时,出口显示器显示车牌号和停车时长
    • 解析:还得加显示器的类,通过显示器拿到车牌号和记录的驶入时间,然后用当前时间减去这个事件就拿到了停车时长

uml02

// 车
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

react1

React.createElement使用工厂模式的好处:如果我们不用 createElement 封装 new VNode(tag,attrs, children),在对生成VNode示例时我们还是让用户去验证各个属性参数,显示不合理,而且用了工厂模式后用户根本不关系内部构造函数怎么变化。

vue异步组件

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

single

js-single

jQuery永远只有一个

设计 原则 验证

  • 符合单一职责原则,只实例化唯一的对象
  • 没法具体开放封闭原则,但是绝对不违反开放封闭原则

第6章 适配器模式

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

dd

d1

/**
 * 适配器模式
 */

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


第 14 章 推荐

回到顶部