开发中,沟通成本是很高的,但如果人人都懂设计模式,不仅能提升可读性降低我们的沟通成本,而且能很好地提升可维护性与可扩展性。
-
学完这篇文章可以得到什么?
-
看完这篇文章之后,相信你能理解各种源码中的设计
-
你可以更好的设计自己的项目,函数,模块间的关系等等
-
下面将以Typescript
结合oop
的方式,与大家一同理解前端中常见的设计模式。
注意:设计模式不是特定于面向对象,以及语言的,它更多的是一种思想。它就像空气,你看不见摸不到,但是你依赖它。
-
设计模式不常用,但它们的设计思想很有指导意义
-
ts和oop(面向对象)非常相配
-
要学设计模式,先学面向对象
-
ts(思维逻辑) + UML(视觉映像) = 设计模式
-
OOP - Object Oriented Program
-
将抽象的编程概念,想象成一个个对象,更好理解
-
现在依然是主流
-
类:模板(如:React、Vue的组件模板)
-
对象:实例(组件实例)
三要素
- 封装-高内聚,低耦合
-
public
-
protected
class内部,子class内部可访问 -
private
class内部可访问
-
-
继承-抽离公共代码,实现代码复用
-
多态-更好的扩展性,复写、重载
实现关系
-
class
实现interface
interface Interface1 { method1(): void; } interface Interface2 { method2(): void; } class MyClass implements Interface1, Interface2 { method1(): void { } method2(): void { } }
-
类实现抽象类
abstract class MyAbstractClass { abstract method1(): void; abstract method2(): void; } class MyClass extends MyAbstractClass { method1(): void { } method2(): void { } }
泛化关系
-
class的泛化关系
class 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(); cat.meow();
-
接口的泛化关系
interface 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(); cat.meow();
关联关系
雇员,有一张工卡
聚合关系
整体包含部分,部分可以脱离整体而单独存在
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();
组合关系
整体包含部分,部分不可以脱离整体
class Mouth() {
eat() {
console.log('吃东西')
}
}
class Face() {
mouth: Mouth
constructor() {
this.mouth = new Mouth()
}
}
依赖关系
不是属性关系,而是函数参数或返回值
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)
ESM的import,CommonJS的require本身就是依赖关系的一种体现
-
SOLID五大设计原则
-
Unix/Linux设计哲学
-
重点关注_开放封闭原则_
S单一职责原则
-
每个程序都做好一件事
-
功能太多了就要拆分
-
每个部分保持相互独立
O开放封闭原则
-
对扩展开放
-
对修改封闭
-
需求发生变化时,通过扩展来解决,而非改动
L李氏置换原则
-
子类能够覆盖父类
-
父类出现的地方,子类也能出现
-
TS的interface就能很好体现这个设计原则,只要满足了interface,就能使用
I接口隔离原则
-
保持接口的单一独立
-
避免出现“胖接口”
-
(和单一职责原则类似),一个接口,一个api只做一个功能
D依赖倒置原则
-
面向接口编程
-
而非面向实例
经典设计模式有23种之多,但我们只要掌握其中常见的那些设计模式,就可以做到万变不离其宗,看到别的设计模式也能很快掌握。
-
创建型
-
工厂模式
-
单例模式
-
原型模式
-
-
结构型
-
代理模式
-
装饰器模式
-
-
行为型
-
观察者模式
-
迭代器模式
-
策略模式
-
工厂模式
如果遇到一个new Class
的地方,就要考虑工厂模式
工厂模式解决了什么问题?
-
创建对象的一种方式。不用每次都亲自创建对象,而是通过一个既定的“工厂”来生产对象。把创建者和
class
_分离_,符合开放封闭原则 -
创建的class来自哪,由工厂本身决定,外部无需知道,外部只需要让工厂知道创建什么
-
工厂和类分离,解耦
-
可以扩展多个类(派生类,平行类)
-
工厂的创建逻辑也可以自由扩展
实现
UML关系:Creator 依赖 Product
简易版本
* @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)
}
}
const factory = new Factory()
const hamburger = factory.create('hamburger')
const hotdot = factory.create('hotdog')
console.log(hamburger, hotdot)
标准版本
* @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]()
}
}
const factory = new Factory()
const hamburger = factory.create('hamburger')
const hotdot = factory.create('hotdog')
应用场景
-
Jquery的$
-
Vue的createElementVNode
-
React的createElementt
-
日常项目开发中,遇到
new class
的场景,要考虑是否可用工厂模式。
单例模式
-
前端对单例模式并不常用,但单例的思想随处可见
-
一个对象/实例 只能被创建一次
-
创建之后缓存起来,以后继续使用
-
即,一个系统中只有一个
单例模式解决了什么问题?
一个系统中就只有一个,保证唯一性
实现
class内部使用私有静态属性(类属性)保存单例,通过静态方法(类方法)获取单例
TS版本
* @name: 单例模式
* @description: 一个系统中仅能有一个
*/
class SingleTon {
name: string
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)
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)
借助模块化实现
let instance
class SingleTon {
}
export default () => {
if (instance == null) {
instance = new SingleTon()
}
return instance
}
模块化后,该模块加载到浏览器中的时候,会以函数的形式挂载到全局上面,因此instance实际也是存在于一个函数作用域内
应用场景
-
登录框,当前用户,购物车,遮罩
-
Vuex,Redux,EventBus
设计原则验证
内部封装getInstance,高内聚,低耦合,分离使用者与class
观察者模式
观察者模式后面又引出了_发布订阅模式_
观察者模式解决了什么问题?
前端最常见,交互,生命周期的触发
实现
* @name: 观察者模式
* @description: 对某个主题创建观察者,当发生变化的时候,通知观察者
*/
* @description: 主题
*/
class Subject {
private state = 0
private observers: Observer[] = []
getState(): number {
return this.state
}
setState(newState: number) {
this.state = newState
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
}
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类图关系:依赖关系
* @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树
基本使用 + 语法
* @name: 生成器
* @description: 这个文件里讲的并非设计模式,而是学习完'迭代器模式'后,对生成器的一个理解
*/
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 (const num of numsIterator) {
console.log(num)
}
console.log([...numsIterator])
function* genNums2() {
yield* [11, 21, 31]
}
const numsIterator2 = genNums2()
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)
使用generator + yield 遍历DOM树
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基础,必须掌握
-
属性描述符日常不会直接使用,但它是理解对象属性的重要基础
原型模式解决了什么问题?
原型模式解决了快速克隆的问题。
实现
* @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实现
* @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关系:关联
* @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
* @name: 装饰Class
* @description: 使用ts的装饰器语法糖装饰class
*/
function testable(target: any) {
target.isTestable = false
}
@testable
class Foo {
static isTestable?: boolean
}
console.log(Foo.isTestable)
接收参数的装饰器
* @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)
装饰 class中的method
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('添加头衔 大大王')
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)
const foo = new Foo()
console.log(foo.getName())
设计原则验证
-
装饰器和目标分离,解耦
-
装饰器可以自由扩展
-
目标也可以自由扩展
-
高内聚,低耦合
代理模式
-
针对一个对象
-
设置代理,控制对这个对象的访问
-
为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题
代理模式解决了什么问题?
凡事不经自己手,转由他人代劳
与装饰器模式的区别
装饰器模式不允许改变原有行为,而代理模式可以改变原有行为
实现
UML类图关系: 关联。
class实现
* @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
* @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
}
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)
console.log(agent.age)
console.log(agent.price)
agent.price = 100 * 10000
console.log(agent.price)
ES6 Proxy使用场景
-
跟踪属性访问——vue3 数据响应式 Proxy
-
隐藏属性
-
验证属性
-
记录实例
Proxy 可能遇到的坑
-
捕获其不变式——不允许通过代理进行会对原有的属性描述符产生改变的行为,如:将属性由
number
转为string
-
关于this,对一个对象进行了
Proxy
行为,那么this指向new proxy
生成的对象,this只有执行的时候才知道是什么
应用场景
- DOM事件代理(事件委托,冒泡给顶层处理)
-
事件绑定到父容器上,而非目标节点
-
适合目标较多或数量不确定(如无限加载的瀑布流图片列表)
-
-
Webpack DevServer proxy 正向代理(客户端代理)
-
Nginx 反向代理(服务器代理)
设计原则验证
-
代理和目标分离,解耦
-
代理可以自行扩展
-
目标也可以自行扩展
职责链模式
-
一个流程,需要多个步骤来处理
-
把多个步骤分开,通过一个“链”串联起来
-
各个步骤相互分离,互不干扰
前端最常见的就是链式操作。
场景
jQuery 链式操作
$('#div1')
.show()
.css('color', 'red')
.append($('#p1'))
Promise的链式操作
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
-
将每个分支单独处理,相互隔离
场景
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 用户购买')
}
}
}
使用策略模式
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
场景
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本身就是对适配器模式的一种实践
{
data() {
return {
userList: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
}
},
computed: {
userNameList() {
this.userList.map(user => user.name)
}
}
}
-
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
实现
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
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 SpecialCar('迈腾', 'B333444')
const trip = new Trip(car)
trip.start()
trip.end()
停车场模拟
-
某停车场,分3层,每层100车位
-
每个车位可以监控车辆的进入和离开
-
车辆进入前,显示每层的空余车位数量
-
车辆进入时,摄像头可以识别车牌号和时间
-
车辆出来时,出口显示器显示车牌号和停车时长
分析数据模型
注意:根据给定条件完成设计,不要自己假定条件增加难度
实现
* @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()
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[] = []
for (let i = 0; i < 3; i++) {
const places: ParkPlace[] = []
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种的数据响应式本身是基于_代理模式,观察者模式的,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
提供了一个简单的语法来封装复杂的条件判断逻辑,使得操作元素的外观变得简单和易于理解。
学习设计模式不仅仅是学习一些具体的代码模板,更重要的是培养一种思考和解决问题的方法。它帮助我们构建可维护、可扩展和高质量的软件,同时提高团队的协作效率。