解密编程世界的秘籍:设计模式之道

开发中,沟通成本是很高的,但如果人人都懂设计模式,不仅能提升可读性降低我们的沟通成本,而且能很好地提升可维护性与可扩展性。

  • 学完这篇文章可以得到什么?

    1. 看完这篇文章之后,相信你能理解各种源码中的设计

    2. 你可以更好的设计自己的项目,函数,模块间的关系等等

下面将以Typescript结合oop的方式,与大家一同理解前端中常见的设计模式。

注意:设计模式不是特定于面向对象,以及语言的,它更多的是一种思想。它就像空气,你看不见摸不到,但是你依赖它。

  • 设计模式不常用,但它们的设计思想很有指导意义

  • ts和oop(面向对象)非常相配

  • 要学设计模式,先学面向对象

  • ts(思维逻辑) + UML(视觉映像) = 设计模式

  • OOP - Object Oriented Program

  • 将抽象的编程概念,想象成一个个对象,更好理解

  • 现在依然是主流

  • 类:模板(如:React、Vue的组件模板)

  • 对象:实例(组件实例)

三要素

  1. 封装-高内聚,低耦合
    • public

    • protected class内部,子class内部可访问

    • private class内部可访问

  2. 继承-抽离公共代码,实现代码复用

  3. 多态-更好的扩展性,复写、重载

实现关系

  1. class实现interface

    interface Interface1 {
       method1(): void;
     }
    
     interface Interface2 {
       method2(): void;
     }
    
     class MyClass implements Interface1, Interface2 {
       method1(): void {
         
       }
    
       method2(): void {
         
       }
     }
    
    
    
  2. 类实现抽象类

    abstract class MyAbstractClass {
      abstract method1(): void;
      abstract method2(): void;
    }
    
    class MyClass extends MyAbstractClass {
      method1(): void {
        
      }
    
      method2(): void {
        
      }
    }
    
    

泛化关系

  1. 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(); 
    
    
  2. 接口的泛化关系

    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种之多,但我们只要掌握其中常见的那些设计模式,就可以做到万变不离其宗,看到别的设计模式也能很快掌握。

  1. 创建型

    • 工厂模式

    • 单例模式

    • 原型模式

  2. 结构型

    • 代理模式

    • 装饰器模式

  3. 行为型

    • 观察者模式

    • 迭代器模式

    • 策略模式

工厂模式

如果遇到一个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主动触发的

  • 各种异步回调,如:setTimeoutPromise.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 VS for循环,不需要数据的长度,不需要知道元素的结构,不需要知道数据的结构,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.allPromise.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...elseswitch...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. 无论什么车,都有车牌号和车辆名称

  3. 价格不同,快车每公里1元,专车每公里2元

  4. 打车时,你要启动行程并显示车辆信息

  5. 结束行程时,显示价格(假定行驶了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

  1. 观察者模式:当数据属性发生变化时,Vue会通过观察者模式通知相关的观察者,从而更新关联的输入元素的值。同时,当用户在输入元素中修改值时,通过观察者模式,Vue会自动更新数据属性的值。

  2. 适配器模式:v-model 指令将数据属性和用户界面中的输入元素绑定在一起,起到了适配器的作用。它将输入元素的值转化为数据属性的值,并将数据属性的改变反映到输入元素,实现了输入和数据之间的双向转换。

  3. 策略模式:v-model 指令根据不同的输入元素类型(如 inputselecttextarea 等)选择不同的策略来处理双向数据绑定。不同类型的输入元素可能有不同的值的获取方式和事件监听机制,v-model 根据类型选择合适的策略来实现数据的同步

v-if

  1. 策略模式

  2. 观察者模式:Vue使用了观察者模式来实现数据的响应式。在 v-if 指令中,当条件发生改变时,Vue会通过观察者模式通知相关的观察者,从而更新 DOM 中对应的元素。

  3. 组合模式:Vue中的模板语法支持将多个 v-if 同时应用在一个元素上,这样就可以根据多个条件的组合来判断是否渲染该元素。这种组合的方式可以看作是组合模式的应用。

  4. 模板方法模式:Vue的编译器在解析模板时,会根据 v-if 指令的条件来生成相应的渲染函数。这个过程中使用了模板方法模式,即定义一个模板方法,然后在不同的条件下使用不同的具体实现。

  5. 外观模式:v-if 可以隐藏或显示元素,这种隐藏和显示的操作可以看作是对元素的外观进行操作。v-if 提供了一个简单的语法来封装复杂的条件判断逻辑,使得操作元素的外观变得简单和易于理解。

学习设计模式不仅仅是学习一些具体的代码模板,更重要的是培养一种思考和解决问题的方法。它帮助我们构建可维护、可扩展和高质量的软件,同时提高团队的协作效率。

  • 41
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Web面试那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值