前端设计模式

1. 面向对象三要素

◆ 继承,子类继承父类
◆ 封装,数据的权限和保密
◆ 多态,同一接口不同实现

1.1 封装

1.1.1 修饰符

◆ public 完全开放
◆ protected 对子类开放
◆ private 对自己开放

javascript中没有修饰符,这里拿java代码来演示

class People {
  name
  age
  protected weight 11 //定义protected属性
  constructor(name, age) {
    this.name = name
    this.age = age
    this.weight = 120
  }
  eat() {
    alert(`${this.name} eat something`)
  }
  speak() {
    alert(`My name is ${this.name}, age ${this.age}`)
  }
}

let zhang = new People('zhang', 20)
zhang.eat()
zhang.speak()

let wang = new People('wang', 21)
wang.eat()
wang.speak()

class Student extends People {
  number
  prite girlfriend // 定义 private 属性
  constructor(name, age, number) {
    super(name, age)
    this.number = number
    this.girlfriend = "xiaoli"
  }
  study() {
    alert(`${this.name} study`)
  }
  getWeight() {
    alert(`${this.weight}`)
  }
}

let xiaoming = new Student('xiaoming', 10, 'A1')
xiaoming.study()
console.log(xiaoming.number)
let xiaohong = new Student('xiaohong', 11, 'A2')
xiaohong.study()

1.1.2 封装目的

◆ 减少耦合,不该外露的不外露
◆ 利于数据、接口的权限管理
◆ ES6目前不支持,一般认为_开头的属性是private

1.2 多态

◆ 同一个接口,不同表现
◆ JS应用极少
◆ 需要结合java等语言的接口、重写、重载等功能

1.2.1 代码演示

class People {
    constructor(name) {
        this.name = name
    }
    saySomething() {

    }
}
class A extends People {
    constructor(name) {
        super(name)
    }
    saySomething() {
        alert('I am A')
    }
}
class B extends People {
    constructor(name) {
        super(name)
    }
    saySomething() {
        alert('I am B')
    }
}
let a = new A('a')
a.saySomething()
let b = new B('b')
b.saySomething()

1.2.2 多态目的

◆ 保持子类的开放性和灵活性
◆ 面向接口编程(JS引用极少,了解即可)

1.3 为何使用面向对象

程序执行:顺序、判断、循环——结构化
面向对象——数据结构化
对于计算机,结构化的才是最简单的
编程应该简单&抽象

2. UML类图

Unified Modeling Language 统一建模语言
这里推荐两款画图工具:
◆ MS Office visio
https://www.processon.com/

js中类图模型


UML类图模型

根据此模型,我们来画一个上文中People类的类图,如下所示:


People类UML类图

我们再来看一个关系型类图的画法,先看下面的关系代码:

class People {
    constructor(name, house) {
        this.name = name
        this.house = house
    }
    saySomething() {

    }
}

class House {
  constructor(city) {
    this.city = city
  }
  showCity() {
    console.log(`house in ${this.city}`)
  }
}

class A extends People {
    constructor(name, house) {
        super(name, house)
    }
    saySomething() {
        alert('I am A')
    }
}

class B extends People {
    constructor(name, house) {
        super(name, house)
    }
    saySomething() {
        alert('I am B')
    }
}

let aHouse = new House('北京') // {city: "北京"}
let a = new A('aaa', aHouse)
console.log(a) // a有房子
let b = new B('bbb')
console.log(b) // b无房子

这里需要注意的是,aHouse是House new出来的实例,所以aHouse是一个对象{city: "北京"},从原型链上可找到showCity()方法。

这里的关系是People引用了House,A、B继承了People,类图如下(实箭头为引用,空箭头为继承):


image.png

3. SOLID五大设计原则

S-单一职责原则
O-开放封闭原则
L-李氏置换原则
I-接口独立原则
D-依赖导致原则

推荐书籍:UNIX/LINUX设计思想

3.1 单一职责

◆ 一个程序只做好一件事
◆ 如果功能过于复杂就拆分开,每个部分保持独立

3.2 开放封闭

◆ 对扩展开放,对修改封闭
◆ 增加需求时,扩展新代码,而非修改已有代码
◆ 这是软件设计的终极目标

3.3 李氏置换

◆ 子类能覆盖父类
◆ 父类能出现的地方子类就能出现
◆ JS中使用较少(弱类型&继承使用较少)

3.4 接口独立原则

◆ 保持接口的单一独立,避免出现“胖接口”
◆ JS中没有接口( typescript例外),使用较少
◆ 类似于单一职责原则,这里更关注接口

3.5 依赖倒置原则

◆ 面向接口编程,依赖于抽象而不依赖于具体
◆ 使用方只关注接口而不关注具体类的实现
◆ JS中使用较少(没有接口&弱类型)

下面我们用一个Promise的例子来说明SO的体现:

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 =''
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.log (err)
})

每个then只做一件事,体现了单一原则(S),如果有新的需求就添加新的then,体现了开放封闭原则(O)。

4. 前端常用设计模式

4.1 创建型

工厂模式(工厂方法模式,抽象工厂模式,建造者模式)
单例模式
原型模式

4.2 结构型

适配器模式
装饰器模式
代理模式
外观模式

桥接模式
组合模式
享元模式

4.3 行为型

策略模式
迭代器模式

模板方法模式
职责链模式
观察者模式

命令模式

备忘录模式
中介者模式
状态模式
解释器模式
访问者模式

5. 设计模式案例

5.1 打车案例

◆ 打车时,可以打专车或者快车。任何车都有车牌号和名称。
◆ 不同车价格不同,快车每公里1元,专车每公里2元。
◆ 行程开始时,显示车辆信息
◆ 行程结束时,显示打车金额(假定行程就5公里)

打车案例UML类图
// 车
class Car {
    constructor(number, name) {
        this.number = number // 车牌
        this.name = name // 名称
    }
}
class Kuaiche extends Car {
    constructor(number, name) {
        super(number, name)
        this.price = 1
    }
}
class Zhuanche extends Car {
    constructor(number, name) {
        super(number, name)
        this.price = 2
    }
}

class Trip {
    constructor(car) {
        this.car = car
    }
    start() {
        console.log(`行程开始,名称: ${this.car.name}, 车牌号: ${this.car.price}`)
    }
    end() {
        console.log('行程结束,价格: ' + (this.car.price * 5))
    }
}

let car = new Kuaiche(100, '桑塔纳')
let trip = new Trip(car)
trip.start()
trip.end()

5.2 停车场案例

◆ 某停车场,分3层,每层100车位
◆ 每个车位都能监控到车辆的驶入和离开
◆ 车辆进入前,显示每层的空余车位数量
◆ 车辆进入时,摄像头可识别车牌号和时间
◆ 车辆出来时,出口显示器显示车牌号和停车时长

停车场UML类图
// 车
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 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
    }
}

// 停车场
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')
    }
}

// 测试代码------------------------------
// 初始化停车场
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)

5.3 购物车

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 高质量Javascript Javascript特性:面向对象,无类,原型 可维护的代码(可读;一致;可预测;看起...
    前端一菜鸟阅读 490评论 0 2
  • 一、6种设计模式 构造函数constructor模式 构造函数模式是创建特定类型的对象的一种模式,把私有属性绑定到...
    yuhuan121阅读 386评论 0 2
  • 1.写出 构造函数模式、混合模式、模块模式、工厂模式、单例模式、发布订阅模式的范例。 构造函数模式 构造函数模式是...
    hellowade阅读 298评论 0 0
  • 1. 写出 构造函数模式、混合模式、模块模式、工厂模式、单例模式、发布订阅模式的范例。 构造函数模式 写一个函数,...
    萧雪圣阅读 264评论 0 0
  • 模块模式 工厂模式 构造函数模式 混合模式 单例模式 发布订阅模式 模块模式 用于模块封装,用立即执行的函数嵌套一...
    辉夜乀阅读 326评论 0 0