前言
大家好,我是见信,一个喜欢学以致用,喜欢分享的boy,希望大家能看了我的文章能有所获,想要点赞,收藏,关注

为什么要学习设计模式?
开发中,沟通成本是很高的,但如果人人都懂设计模式,不仅能提升可读性降低我们的沟通成本,而且能很好地提升可维护性与可扩展性。
-
学完这篇文章可以得到什么?
- 看完这篇文章之后,相信你能理解各种源码中的设计
- 你可以更好的设计自己的项目,函数,模块间的关系等等
下面将以Typescript
结合oop
的方式,与大家一同理解前端中常见的设计模式。
注意:设计模式不是特定于面向对象,以及语言的,它更多的是一种思想。它就像空气,你看不见摸不到,但是你依赖它。
预备
- 设计模式不常用,但它们的设计思想很有指导意义
- ts和oop(面向对象)非常相配
- 要学设计模式,先学面向对象
- ts(思维逻辑) + UML(视觉映像) = 设计模式
面向对象
- OOP - Object Oriented Program
- 将抽象的编程概念,想象成一个个对象,更好理解
- 现在依然是主流
- 类:模板(如:React、Vue的组件模板)
- 对象:实例(组件实例)
三要素
- 封装-高内聚,低耦合
public
protected
class内部,子class内部可访问private
class内部可访问
- 继承-抽离公共代码,实现代码复用
- 多态-更好的扩展性,复写、重载
UML类图
实现关系

-
class
实现interface
tsinterface Interface1 { method1(): void; } interface Interface2 { method2(): void; } class MyClass implements Interface1, Interface2 { method1(): void { // 实现 Interface1 中的 method1 的具体逻辑 } method2(): void { // 实现 Interface2 中的 method2 的具体逻辑 } }
-
类实现抽象类
tsabstract class MyAbstractClass { abstract method1(): void; abstract method2(): void; } class MyClass extends MyAbstractClass { method1(): void { // 实现 method1 的具体逻辑 } method2(): void { // 实现 method2 的具体逻辑 } }
泛化关系

-
class的泛化关系
tsclass Animal { name: string; constructor(name: string) { this.name = name; } eat(): void { console.log("Eating"); } } class Cat extends Animal { meow(): void { console.log("Meowing"); } } const cat = new Cat("Kitty"); cat.eat(); // 输出 "Eating" cat.meow(); // 输出 "Meowing"
-
接口的泛化关系
tsinterface Animal { name: string; eat(): void; } interface Cat extends Animal { meow(): void; } const cat: Cat = { name: "Kitty", eat() { console.log("Eating"); }, meow() { console.log("Meowing"); } }; cat.eat(); // 输出 "Eating" cat.meow(); // 输出 "Meowing"
关联关系
雇员,有一张工卡
聚合关系
整体包含部分,部分可以脱离整体而单独存在
ts
class Engine {
start() {
console.log("Engine started");
}
}
class Car {
engine: Engine;
constructor(engine: Engine) {
this.engine = engine;
}
start() {
this.engine.start();
console.log("Car started");
}
}
const engine = new Engine();
const car = new Car(engine);
car.start(); // Engine started Car started
组合关系
整体包含部分,部分不可以脱离整体
ts
class Mouth() {
eat() {
console.log('吃东西')
}
}
class Face() {
mouth: Mouth
constructor() {
this.mouth = new Mouth()
}
}
依赖关系
不是属性关系,而是函数参数或返回值
ts
class Driver {
name: string
drivingLicence: DrivingLicence
constructor(name: string) {
this.name = name
this.drivingLicence = new DrivingLicence(105)
}
drive(car: Car) {
console.log(`${this.name}驾驶${car.name}`)
}
sendDrivingLicence() {
return this.drivingLicence
}
}
class Car {
name: string
constructor(name: string) {
this.name = name
}
}
class DrivingLicence {
id: number
constructor(id: number) {
this.id = id
}
}
const driver = new Driver('老王')
driver.drive(new Car('奥托')) // 老王驾驶奥托
console.log(driver.sendDrivingLicence().id) // 105
ESM的import,CommonJS的require本身就是依赖关系的一种体现
设计原则
- SOLID五大设计原则
- Unix/Linux设计哲学
- 重点关注开放封闭原则
S单一职责原则
- 每个程序都做好一件事
- 功能太多了就要拆分
- 每个部分保持相互独立
O开放封闭原则
- 对扩展开放
- 对修改封闭
- 需求发生变化时,通过扩展来解决,而非改动
L李氏置换原则
- 子类能够覆盖父类
- 父类出现的地方,子类也能出现
- TS的interface就能很好体现这个设计原则,只要满足了interface,就能使用
I接口隔离原则
- 保持接口的单一独立
- 避免出现"胖接口"
- (和单一职责原则类似),一个接口,一个api只做一个功能
D依赖倒置原则
- 面向接口编程
- 而非面向实例
介绍设计模式
经典设计模式有23种之多,但我们只要掌握其中常见的那些设计模式,就可以做到万变不离其宗,看到别的设计模式也能很快掌握。
-
创建型
- 工厂模式
- 单例模式
- 原型模式
-
结构型
- 代理模式
- 装饰器模式
-
行为型
- 观察者模式
- 迭代器模式
- 策略模式
工厂模式
如果遇到一个new Class
的地方,就要考虑工厂模式
工厂模式解决了什么问题?
- 创建对象的一种方式。不用每次都亲自创建对象,而是通过一个既定的"工厂"来生产对象。把创建者和
class
分离,符合开放封闭原则 - 创建的class来自哪,由工厂本身决定,外部无需知道,外部只需要让工厂知道创建什么
- 工厂和类分离,解耦
- 可以扩展多个类(派生类,平行类)
- 工厂的创建逻辑也可以自由扩展
实现

UML关系:Creator 依赖 Product
简易版本
js
/**
* @name:工厂模式
* @description: 分离创建者与class
*/
/**
* 产品
*/
class Product {
public name: string
constructor(name: string) {
this.name = name
}
fn1() {
console.info('执行了 fn1')
}
fn2() {
console.info('执行了 fn2')
}
}
/**
* 工厂
*/
class Factory {
create(name: string): Product {
return new Product(name)
}
}
// test
const factory = new Factory()
const hamburger = factory.create('hamburger')
const hotdot = factory.create('hotdog')
console.log(hamburger, hotdot)
标准版本
js
/**
* @name:标准工厂模式
* @description: 分离创建者与class
*/
interface IProduct {
name: string
fn1: () => void
fn2: ()=> void
}
/**
* 汉堡
*/
export class Hamburger implements IProduct {
public name: string
constructor(name: string) {
this.name = name
}
fn1() {
console.info('执行了 fn1')
}
fn2() {
console.info('执行了 fn2')
}
}
/**
* 热狗
*/
export class Hotdog implements IProduct {
public name: string
constructor(name: string) {
this.name = name
}
fn1() {
console.info('执行了 fn1')
}
fn2() {
console.info('执行了 fn2')
}
}
type CreateType = 'hamburger' | 'hotdog'
/**
* 标准工厂
*/
class Factory {
private typeMethod: Record<CreateType, () => Hamburger | Hotdog>
constructor() {
this.typeMethod = {
hamburger: () => new Hamburger('hamburger'),
hotdog: () => new Hotdog('hotdog')
}
}
create(type: CreateType): IProduct {
return this.typeMethod[type]()
}
}
// test
const factory = new Factory()
const hamburger = factory.create('hamburger')
const hotdot = factory.create('hotdog')
应用场景
- Jquery的$
- Vue的createElementVNode
- React的createElementt
- 日常项目开发中,遇到
new class
的场景,要考虑是否可用工厂模式。
单例模式
- 前端对单例模式并不常用,但单例的思想随处可见
- 一个对象/实例 只能被创建一次
- 创建之后缓存起来,以后继续使用
- 即,一个系统中只有一个
单例模式解决了什么问题?
一个系统中就只有一个,保证唯一性
实现
class内部使用私有静态属性(类属性)保存单例,通过静态方法(类方法)获取单例
TS版本
js
/**
* @name: 单例模式
* @description: 一个系统中仅能有一个
*/
class SingleTon {
name: string
// 私有化constructor,防止new操作
private constructor(name: string) {
this.name = name
}
private static instance: SingleTon | null
static getInstance(name: string): SingleTon {
if (SingleTon.instance == null) {
SingleTon.instance = new SingleTon(name)
}
return SingleTon.instance
}
}
const s1 = SingleTon.getInstance('张三') //正确获取单例对象的方式
const s2 = SingleTon.getInstance('李四')
console.log(s1 === s2) // true
// const s1 = new SingleTon('zhangsan') // 报错,类"SingleTon"的构造函数是私有的,仅可在类声明中访问(ts 2673)
JS版本
js
function genGetInstance() {
let instance
class SingleTon {
}
return () => {
if (instance == null) {
instance = new SingleTon()
}
return instance
}
}
const getInstance = genGetInstance()
const s1 = getInstance()
const s2 = getInstance()
console.log(s1 === s2)
借助模块化实现
js
// 使用模块化实现 - commonjs ES6 Module
// 假设这是一个文件,getInstance.js
let instance
class SingleTon {
}
export default () => {
if (instance == null) {
instance = new SingleTon()
}
return instance
}
模块化后,该模块加载到浏览器中的时候,会以函数的形式挂载到全局上面,因此instance实际也是存在于一个函数作用域内
应用场景
- 登录框,当前用户,购物车,遮罩
- Vuex,Redux,EventBus
设计原则验证
内部封装getInstance,高内聚,低耦合,分离使用者与class
观察者模式
观察者模式后面又引出了发布订阅模式
观察者模式解决了什么问题?
前端最常见,交互,生命周期的触发
实现

js
/**
* @name: 观察者模式
* @description: 对某个主题创建观察者,当发生变化的时候,通知观察者
*/
/**
* @description: 主题
*/
class Subject {
private state = 0
private observers: Observer[] = []
getState(): number {
return this.state
}
setState(newState: number) {
this.state = newState
// state变化时,通知所有观察者
this.notify()
}
// 添加 观察者
attach(observer: Observer) {
this.observers.push(observer)
}
private notify() {
this.observers.forEach((observer) => {
observer.update(this.state)
})
}
}
/**
* @description: 观察者
*/
class Observer {
name: string
constructor(name: string) {
this.name = name
}
// 当发生变化时,subject调用notify,内部触发各个观察者的update,观察者得到通知
update(state: number) {
console.log(`${this.name}接收到了新的变化,当前state变化为了${state}`)
}
}
const subject = new Subject()
const xiaoming = new Observer('小明')
const xiaohong = new Observer('小红')
const xiaogang = new Observer('小刚')
subject.attach(xiaogang)
subject.attach(xiaohong)
subject.attach(xiaoming)
subject.setState(1)
subject.setState(2)
subject.setState(3)
应用场景
- DOM事件
- Vue React组件生命周期
- Vue的
watch
- Vue的组件更新过程,React不是观察者模式,它是通过
setState
主动触发的 - 各种异步回调,如:
setTimeout
,Promise.then
- MutationObserver,监听DOM节点,DOM树变化的
- NodeJS的stream




设计原则验证
- Observer 和 Subject 分离、解耦
- Subject 可以自由扩展
- Observer 可以自由扩展
发布订阅模式
- 观察者模式是被动的,无法主动触发。
- 发布订阅模式,是可以主动触发的。
- 是观察者模式的另一种实现模式。


区别
- 观察者模式:Subject 和 Observer 直接绑定的,中间无媒介
- 发布订阅模式:Publisher 和 Subscriber 互不相识,中间有媒介
- 看是否需要手动触发emit
应用场景
- 自定义事件,Vue2本身就是一个EventBus,Vue3不再自带EventBus功能,推荐使用mitt
- postMessage通讯
- webWorker通讯
- webSocket通讯
迭代器模式
for
循环不是迭代器- 迭代器是用来解决
for
循环的问题的
迭代器模式解决了什么问题?
for
循环的触发,需要知道数组长度,需要知道如何获取元素forEach
VSfor
循环,不需要数据的长度,不需要知道元素的结构,不需要知道数据的结构,forEach
是一个简易的迭代器
迭代器解决了如何更加方便、简易地遍历一个有序的数据集合的问题
- 顺序访问有序结构(如:数组、NodeList)
- 不知道数据的长度、内部结构
- 高内聚、低耦合
js中有序的数据结构
- 数组
- 字符串
- NodeList等DOM集合
- Map
- Set
- arguments 类数组
注意:Object是无序结构
实现
UML类图关系:依赖关系
js
/**
* @name: 迭代器模式
*/
class DataIterator {
private data: number[]
private index = 0
constructor(container: DataContainer) {
this.data = container.data
}
next(): number | null {
const index = this.index
this.index++
return this.data[index]
}
hasNext(): boolean {
if (this.index >= this.data.length) return false
return true
}
}
class DataContainer {
data: number[] = [10, 20, 30, 40, 50]
// 获取迭代器
getIterator() {
return new DataIterator(this)
}
}
const dataContainer = new DataContainer()
const iterator = dataContainer.getIterator()
while (iterator.hasNext()) {
const num = iterator.next()
console.log(num)
}
应用场景
Symbol.iterator
。所有的有序数据结构,都内置了Symbol.iterator
这个key,使用它可以获得该数据结构的迭代器- 自定义迭代器
- 用于
for of
,只要内置了Symbol.iterator
这个key,都可以使用for of
来进行遍历 - 用于数组的解构、扩展操作符、
Array.from
- 用于
Promise.all
和Promise.race
- 用于生成器
yield*
设计原则验证
- 使用者和目标分离,解耦
- 目标能自行控制其内部逻辑
- 使用者不关心目标的内部结构
Generator 生成器
- 基本使用
yield*
语法yield
遍历DOM树
基本使用 + 语法
js
/**
* @name: 生成器
* @description: 这个文件里讲的并非设计模式,而是学习完'迭代器模式'后,对生成器的一个理解
*/
//#region 使用 yield 生成迭代器 ----------------------------------------
function* genNums() {
yield 10
yield 20
yield 30
}
// 生成器的本质就是返回一个迭代器
const numsIterator = genNums()
// 所以我们可以通过迭代器的方式去应用
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator)
// 也可以通过for of去使用
for (const num of numsIterator) {
console.log(num)
}
// 也可以使用扩展操作符
console.log([...numsIterator])
//#endregion
//#region 使用 yield* 生成迭代器-----------------------------------
function* genNums2() {
// yield* 后面跟的需要是一个有序结构,这个有序结构本身已经实现了[Symbol.Iterator]
yield* [11, 21, 31]
}
const numsIterator2 = genNums2()
// 也可以通过for of去使用
for (const num of numsIterator2) {
console.log(num)
}
// 也可以使用扩展操作符
console.log([...numsIterator2])
console.log(numsIterator2.next())
console.log(numsIterator2.next())
console.log(numsIterator2.next())
console.log(numsIterator2.next())
console.log(numsIterator2)
//#endregion
使用generator + yield 遍历DOM树
js
function* traverse(elemList: Array<Element>): any {
for (const elem of elemList) {
yield elem
const children = Array.from(elem.children)
if (children.length) {
yield* traverse(children)
}
}
}
原型模式
- 原型模式不常用,但原型链是JS基础,必须掌握
- 属性描述符日常不会直接使用,但它是理解对象属性的重要基础
原型模式解决了什么问题?
原型模式解决了快速克隆的问题。
实现

js
/**
* @name: 原型模式
* @description: 快速克隆自己
*/
class CloneDemo {
name = 'clone demo'
clone(): CloneDemo {
return new CloneDemo()
}
}
JS原型的基础知识 prototype
和 __proto__
- 函数、class都有显示原型
prototype
- 对象都有隐式原型
__proto__
- 对象
__proto__
指向其构造函数的prototype

应用场景
最符合原型模式的应用场景就是Object.create
,它可以指定原型
装饰器模式
- 装饰器本身就是一个函数,它可以针对对象、class、函数
- 动态的添加新功能
- 但不改变它原有的功能
装饰器模式解决了什么问题?
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
实现
js实现
js
/**
* @name: 装饰器模式
*/
const phone: Record<string, any> = {
name: 'phone1',
fn1() {
console.log('fn1')
}
}
function decorator(phone: Record<string, any>) {
phone.fn2 = () => {
console.log('fn2')
}
}
decorator(phone)
phone.fn1()
phone.fn2()
class实现
UML关系:关联
js
/**
* @name: 装饰器模式(面向对象)
* @description: 为circle添加一个装饰器
*/
class Circle {
draw() {
console.log('画一个圆形')
}
}
class Decorator {
private circle: Circle
constructor(circle: Circle) {
this.circle = circle
}
draw() {
this.circle.draw() // 原有功能
this.setBorder() // 装饰
}
private setBorder() {
console.log('设置边框颜色')
}
}
const circle = new Circle()
circle.draw()
const de = new Decorator(circle)
de.draw()
装饰 class
js
/**
* @name: 装饰Class
* @description: 使用ts的装饰器语法糖装饰class
*/
function testable(target: any) {
target.isTestable = false
}
@testable
class Foo {
static isTestable?: boolean
}
console.log(Foo.isTestable) // false
接收参数的装饰器
js
/**
* @name: 装饰Class
* @description: 使用ts的装饰器语法糖装饰class
*/
function testable(val: boolean) {
return function (target: any) {
target.isTestable = val
}
}
@testable(true)
class Foo {
static isTestable?: boolean
}
console.log(Foo.isTestable) // true
装饰 class中的method
js
function testable(val: boolean) {
return function (target: any) {
target.isTestable = val
}
}
// 添加一个头衔
function honor(target: any , key: string, descriptor: PropertyDescriptor) {
console.log(target === Foo.prototype)
const oldValue = descriptor.value
descriptor.value = function () {
// 先打印头衔
console.log('添加头衔 大大王')
// eslint-disable-next-line prefer-rest-params
return oldValue.apply(this, arguments)
}
}
@testable(true)
class Foo {
private name = '张三'
private age = 20
static isTestable?: boolean
constructor() {
console.log(1)
}
@honor
getName() {
return this.name
}
getAge() {
return this.age
}
}
console.log(Foo.isTestable) // true
const foo = new Foo()
console.log(foo.getName())
设计原则验证
- 装饰器和目标分离,解耦
- 装饰器可以自由扩展
- 目标也可以自由扩展
- 高内聚,低耦合
代理模式
- 针对一个对象
- 设置代理,控制对这个对象的访问
- 为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题
代理模式解决了什么问题?
凡事不经自己手,转由他人代劳
与装饰器模式的区别
装饰器模式不允许改变原有行为,而代理模式可以改变原有行为
实现
UML类图关系: 关联。
class实现
js
/**
* @name:代理模式
* @description:
*/
class RealImg {
fileName: string
constructor(fileName: string) {
this.fileName = fileName
}
display() {
this.loadFromDist()
console.log('display...', this.fileName)
}
private loadFromDist() {
console.log('loading...', this.fileName)
}
}
class ProxyImg {
realImg: RealImg
constructor(fileName: string) {
this.realImg = new RealImg(fileName)
}
display() {
// 这里还可以加很多限制
this.realImg.display()
}
}
const proxyImg = new ProxyImg('ljx.jpg')
proxyImg.display()
ES6 Proxy
js
/**
* @name: ES6的Proxy使用
*/
const star = {
name: '张三',
age: 25,
phone: '18454653654',
price: 0 // 明星不谈钱
}
const agent = new Proxy(star, {
get(target, key) {
if (key === 'phone') {
return '经纪人电话' + 18154657811
}
if (key === 'price') {
return 100 * 1000 //报价
}
// 使用 '反射' 操作target
return Reflect.get(target, key)
},
set(target, key, val): boolean {
if (key === 'price') {
if (val < 100 * 1000) {
throw new Error('价格太低了')
} else {
console.log('报价成功,合作愉快!')
return Reflect.set(target, key, val)
}
}
// 其他属性不可设置
return false
}
})
console.log(agent.name) // 张三
console.log(agent.phone) // 经纪人电话18154657811
console.log(agent.age) // 25
console.log(agent.price) // 100000
agent.price = 100 * 10000
console.log(agent.price) // 100000
ES6 Proxy使用场景
- 跟踪属性访问------vue3 数据响应式 Proxy
- 隐藏属性
- 验证属性
- 记录实例
Proxy 可能遇到的坑
- 捕获其不变式------不允许通过代理进行会对原有的属性描述符产生改变的行为,如:将属性由
number
转为string
- 关于this,对一个对象进行了
Proxy
行为,那么this指向new proxy
生成的对象,this只有执行的时候才知道是什么
应用场景
- DOM事件代理(事件委托,冒泡给顶层处理)
- 事件绑定到父容器上,而非目标节点
- 适合目标较多或数量不确定(如无限加载的瀑布流图片列表)
- Webpack DevServer proxy 正向代理(客户端代理)
- Nginx 反向代理(服务器代理)
设计原则验证
- 代理和目标分离,解耦
- 代理可以自行扩展
- 目标也可以自行扩展
职责链模式
- 一个流程,需要多个步骤来处理
- 把多个步骤分开,通过一个"链"串联起来
- 各个步骤相互分离,互不干扰
前端最常见的就是链式操作。
场景
jQuery 链式操作
js
$('#div1')
.show()
.css('color', 'red')
.append($('#p1'))
Promise的链式操作
js
// 加载图片
function loadImg(src: string) {
const promise = new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject('图片加载失败')
}
img.src = src
})
return promise
}
const src = 'https://www.imooc.com/static/img/index/logo_new.png'
const result = loadImg(src)
result.then((img: HTMLImageElement) => {
console.log('img.width', img.width)
return img
}).then((img: HTMLImageElement) => {
console.log('img.height', img.height)
}).catch((err) => {
console.log(err)
})
策略模式
- 针对多个条件分支
- 不用很多
if...else
或switch...case
- 将每个分支单独处理,相互隔离
场景
js
class User {
private type: string
constructor(type: string) {
this.type = type
}
buy() {
const { type } = this
if (type === 'ordinary') {
console.log('普通用户购买')
}
if (type === 'member') {
console.log('会员购买')
}
if (type === 'vip') {
console.log('VIP 用户购买')
}
}
}
使用策略模式
js
interface IUser {
buy: () => void
}
class OrdinaryUser implements IUser {
buy() {
console.log('普通用户购买')
}
}
class MemberUser implements IUser {
buy() {
console.log('会员购买')
}
}
class VipUser implements IUser {
buy() {
console.log('VIP 用户购买')
}
}
const u1 = new OrdinaryUser()
u1.buy()
const u2 = new MemberUser()
u2.buy()
const u3 = new VipUser()
u3.buy()
适配器模式
- 我们需要使用某种特定格式、类型的数据
- 而我们获取到的原始数据,并非这种格式
- 因此我们需要转换,如vue的
computed
场景
js
// 电源插口
class Source {
supply() {
return '220V 电源'
}
}
// 适配器
class Adapter {
source = new Source()
adaptedSupply() {
const sourceRes = this.source.supply()
return `${sourceRes} --> 12V 电源`
}
}
// 手机使用
const adapter = new Adapter()
const res = adapter.adaptedSupply()
console.log(res)
vue的computed本身就是对适配器模式的一种实践
js
// Vue 组件配置
{
data() {
return {
userList: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
}
},
computed: {
userNameList() {
this.userList.map(user => user.name) // ['张三', '李四', ... ]
}
}
}
MVC 和 MVVM
- MVC 和 MVVM 不属于经典的 23 种设计模式,但也可以说他们是设计模式。
- 本来设计模式就是一种抽象的定义,而且随着时代的发展,它也需要慢慢的改变。
- 如何称呼无所谓,关键是理解它们的内容
MVC原理
- View 传送指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈

MVVM原理
- View 即 Vue template
- Model 即 Vue data
- VM 即 Vue 其他核心功能,负责 View 和 Model 通讯,即各种指令,如v-bind,v-model
如何设计?
- 先详细审查需求,抓住细节和重点
- 分析
- 抽象数据模型,梳理关系,定义属性和方法。我们应该定义多少个class,这些class有哪些属性和方法,class之间该如何关联?
- 梳理流程
- 最后画出UML类图,写代码
- 切忌一开始就写代码
- 关注整体设计,别计较细节,把UML类图看的重一些
真题模拟
打车模拟
- 打车时你可以打快车和专车
- 无论什么车,都有车牌号和车辆名称
- 价格不同,快车每公里1元,专车每公里2元
- 打车时,你要启动行程并显示车辆信息
- 结束行程时,显示价格(假定行驶了5公里)
分析数据模型
注意:行程是一个单独的class
实现
js
// 抽象类,必须被子类实现,不能直接new Car()
abstract class Car {
name: string
number: string
// 抽象属性
abstract price: number
constructor(name: string, number: string) {
this.name = name
this.number = number
}
}
/**
* @description: 快车
*/
class ExpressCar extends Car {
price = 1
constructor(name: string, number: string) {
super(name, number)
}
}
/**
* @description: 专车
*/
class SpecialCar extends Car {
price = 2
constructor(name: string, number: string) {
super(name, number)
}
}
/**
* @description: 行程
*/
class Trip {
car: Car // 直接指定未Car类,可以兼容所有的子类(依赖倒置原则)
constructor(car: Car) {
this.car = car
}
start() {
console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.number}`)
}
end() {
console.log('行程结束,价格: ' + (this.car.price * 5))
}
}
// const car = new ExpressCar('桑塔纳', 'A111222')
const car = new SpecialCar('迈腾', 'B333444')
const trip = new Trip(car)
trip.start()
trip.end()
停车场模拟
- 某停车场,分3层,每层100车位
- 每个车位可以监控车辆的进入和离开
- 车辆进入前,显示每层的空余车位数量
- 车辆进入时,摄像头可以识别车牌号和时间
- 车辆出来时,出口显示器显示车牌号和停车时长
分析数据模型

注意:根据给定条件完成设计,不要自己假定条件增加难度
实现
js
/**
* @name:停车场模拟
*/
// 车
export class Car {
number: string
constructor(number: string) {
this.number = number
}
}
// 停车信息
interface IEntryInfo {
number: string
inTime: number
place?: ParkPlace
}
// 入口摄像头
class ParkCamera {
// 拍照
shot(car: Car): IEntryInfo {
return {
number: car.number,
inTime: Date.now()
}
}
}
// 出口显示器
class ParkScreen {
show(info: IEntryInfo) {
const { inTime, number } = info
const duration = Date.now() - inTime
console.log(`车牌号:${number} ,停留时间:${duration}`)
}
}
// 车位
class ParkPlace {
isEmpty = true
getInto() {
this.isEmpty = false
}
out() {
this.isEmpty = true
}
}
// 层
class ParkFloor {
index: number
parkPlaces: ParkPlace[]
constructor(index: number, places: ParkPlace[]) {
this.index = index
this.parkPlaces = places
}
get emptyPlaceNum(): number {
let num = 0
for (const place of this.parkPlaces) {
if (place.isEmpty) num++
}
return num
}
}
// 停车场
class Park {
parkFloors: ParkFloor[]
parkCamera = new ParkCamera()
parkScreen = new ParkScreen()
entryInfoList: Map<string, IEntryInfo> = new Map() // key 是 car.number
constructor(floors: ParkFloor[]) {
this.parkFloors = floors
}
getInto(car: Car) {
// 获取摄像头的信息:车牌号,时间
const entryInfo = this.parkCamera.shot(car)
// 某个车位
const i = Math.round((Math.random() * 100) % 100)
const place = this.parkFloors[0].parkPlaces[i] // 停在第一层的某个车位(想要第二层,第三层,也可以用随机数获取)
// 进入车位
place.getInto()
// 记录停车信息
entryInfo.place = place
this.entryInfoList.set(car.number, entryInfo)
}
out(car: Car) {
// 获取停车信息
const entryInfo = this.entryInfoList.get(car.number)
if (entryInfo == null) return
const { place } = entryInfo
if (place == null) return
// 从车位离开
place.out()
// 出口显示屏,显示
this.parkScreen.show(entryInfo)
// 删除停车信息
this.entryInfoList.delete(car.number)
}
// 当前停车场的空余车位
get emptyInfo(): string {
return this.parkFloors.map(floor => {
return `${floor.index} 层还有 ${floor.emptyPlaceNum} 个车位`
}).join('\n')
}
}
// ---------- 初始化停车场 ----------
const floors: ParkFloor[] = []
// 3 层
for (let i = 0; i < 3; i++) {
const places: ParkPlace[] = []
// 每层 100 个车位
for (let j = 0; j < 100; j++) {
places[j] = new ParkPlace()
}
floors[i] = new ParkFloor(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.emptyInfo)
park.getInto(car1)
console.log('第二辆车即将进入')
console.log(park.emptyInfo)
park.getInto(car2)
console.log('第三辆车即将进入')
console.log(park.emptyInfo)
park.getInto(car3)
console.log('第一辆车离开')
park.out(car1)
console.log('第二辆车离开')
park.out(car2)
console.log('第三辆车离开')
park.out(car3)
console.log(park.emptyInfo)
vue中的常见API用了什么设计模式?
前面已经说过,Vue种的数据响应式本身是基于代理模式 ,观察者模式 的,computed
在此基础之上,还有转换器模式 ,当观察到某个响应式数据变化时,输出特定格式。又比如v-for
本身是基于迭代器模式,后面我们再看一看别的一些api。
v-model
- 观察者模式:当数据属性发生变化时,Vue会通过观察者模式通知相关的观察者,从而更新关联的输入元素的值。同时,当用户在输入元素中修改值时,通过观察者模式,Vue会自动更新数据属性的值。
- 适配器模式:
v-model
指令将数据属性和用户界面中的输入元素绑定在一起,起到了适配器的作用。它将输入元素的值转化为数据属性的值,并将数据属性的改变反映到输入元素,实现了输入和数据之间的双向转换。 - 策略模式:
v-model
指令根据不同的输入元素类型(如input
、select
、textarea
等)选择不同的策略来处理双向数据绑定。不同类型的输入元素可能有不同的值的获取方式和事件监听机制,v-model
根据类型选择合适的策略来实现数据的同步
v-if
- 策略模式
- 观察者模式:Vue使用了观察者模式来实现数据的响应式。在
v-if
指令中,当条件发生改变时,Vue会通过观察者模式通知相关的观察者,从而更新 DOM 中对应的元素。 - 组合模式:Vue中的模板语法支持将多个
v-if
同时应用在一个元素上,这样就可以根据多个条件的组合来判断是否渲染该元素。这种组合的方式可以看作是组合模式的应用。 - 模板方法模式:Vue的编译器在解析模板时,会根据
v-if
指令的条件来生成相应的渲染函数。这个过程中使用了模板方法模式,即定义一个模板方法,然后在不同的条件下使用不同的具体实现。 - 外观模式:
v-if
可以隐藏或显示元素,这种隐藏和显示的操作可以看作是对元素的外观进行操作。v-if
提供了一个简单的语法来封装复杂的条件判断逻辑,使得操作元素的外观变得简单和易于理解。
总结
学习设计模式不仅仅是学习一些具体的代码模板,更重要的是培养一种思考和解决问题的方法。它帮助我们构建可维护、可扩展和高质量的软件,同时提高团队的协作效率。