一、五大设计原则
- S - 单一职责原则(做好一件事)
- O - 开放封闭原则(可扩展,不修改)
- L - 李氏置换原则(子类覆盖父类)
- I - 接口独立原则(单一独立)
- D - 依赖倒置原则(依赖抽象)
二、23种设计模式
- 创建型
- 工厂模式 - 工厂方法模式(创建和生成)√
- 工厂模式 - 抽象工厂模式(创建和生成)√
- 工厂模式 - 建造者模式(创建和生成)√
- 单例模式(只能出现一个对象)√
- 原型模式 (通过拷贝一个现有对象生成新的对象)
- 组合型
- 适配器模式 √
- 装饰器模式 √
- 代理模式 √
- 外观模式 √
- 桥接模式
- 组合模式
- 享元模式
- 行为型
- 策略模式
- 模板方法模式
- 观察者模式 √
- 迭代器模式 √
- 职责连模式
- 命令模式
- 备忘录模式
- 状态模式 √
- 访问者模式
- 中介者模式
- 解释器模式
(一)工厂模式
- 将new操作单独封装
class Product {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
// ...methods
}
class Creator {
create(name) {
return new Product(name)
}
}
const creator = new Creator()
const p1 = creator.create('p1')
p1.getName()
设计原则:
构造函数和创建者分离
开放封闭原则
(四)单例模式
- 只能有一个实例
class SingleObject {
login() {
console.log('login...')
}
}
SingleObject.getInstance = (function() {
let instance = null
return function () {
if (!instance) {
instance = new SingleObject()
}
return instance
}
})()
const obj1 = SingleObject.getInstance()
const obj2 = SingleObject.getInstance()
console.log('obj1 === obj2', obj1 === obj2)
注:js中没有private类型做限制,只能通过注释来告诉使用者通过SingleObject.getInstance()来创建实例,多次创建new SingleObject(),也不会报错
设计原则:
单一职责原则
开放封闭原则
(六)适配器模式
- 旧接口和使用者不兼容
- 中间加一个适配转换接口
场景:旧接口封装、vue computed
class Adaptee {
specificRequest() {
return '德国插头'
}
}
class Target {
constructor() {
this.adaptee = new Adaptee()
}
request() {
const info = this.adaptee.specificRequest()
return `${info} - 转换器 - 中国标准插头`
}
}
const target = new Target()
console.log(target.request())
设计原则:
将旧接口与使用者分离
开放封闭原则
(七)装饰器模式
- 为对象添加新功能
- 不改变其原有的结构和功能
场景:es7装饰器(@babel/plugin-proposal-decorators),core-decorator(第三方开源库)
// 装饰器模式(es6)
class Circle {
draw() {
console.log('画一个圆')
}
}
class Decorator {
constructor(circle) {
this.circle = circle
}
draw() {
this.circle.draw()
this.setRedBorder()
}
setRedBorder() {
console.log('设置红色边框')
}
}
const circle = new Circle()
circle.draw()
console.log('------分割线--------')
const dec = new Decorator(circle)
dec.draw()
// 类装饰器(es7)
@decorator
class Demo {}
function decorator(target) {
target.isDec = true
}
console.log(Demo.isDec)
// 类装饰器(es7) - 传参
@decorator2(false)
class Demo2 {}
function decorator2(isDec) {
return function(target) {
target.isDec = isDec
}
}
console.log(Demo2.isDec)
// 类装饰器(es7) - mixins
const Foo = {
foo() {
console.log('foo')
}
}
function mixins(...list) {
return function(target) {
Object.assign(target.prototype, ...list)
}
}
@mixins(Foo)
class Demo3 {}
const demo3 = new Demo3()
demo3.foo()
// 方法装饰器 - readonly
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
class Person {
constructor() {
this._name = '张三'
}
@readonly
name() {
return this._name
}
}
const person = new Person()
console.log(person.name())
person.name = function() {
return '李四'
}
console.log(person.name()) // 获取到的还是 张三
// 方法装饰器 - log
function log(target, name, descriptor) {
const oldValue = descriptor.value
decorator.value = function() {
console.log(`log: ${name}`, arguments)
return oldValue.apply(this, arguments)
}
return decorator
}
class Math {
@log
add(a, b) {
return a + b
}
}
const math = new Math()
const result = math.add(3, 8)
console.log(result)
设计原则:
将现有对象和装饰器进行分离,两者独立存在
符合开放封闭原则
(八)代理模式
- 使用者无权访问目标对象
- 中间加代理,通过代理做授权和控制
场景:网页事件代理、$.proxy、ES6 Proxy
// 代理模式(es6)
class RealImg {
constructor(fileName) {
this.fileName = fileName
this.loadFromDisk()
}
loadFromDisk() {
console.log('load...', this.fileName)
}
display() {
console.log('display...', this.fileName)
}
}
class ProxyImg {
constructor(fileName) {
this.realImg = new RealImg(fileName)
}
display() {
this.realImg.display()
}
}
const proxyImg = new ProxyImg('1.png')
proxyImg.display()
// 代理模式($.proxy)
$('#app').click(function() {
var fn = function() {
$(this).css('background', '#f00')
}
setTimeout($.proxy(fn, this), 1000)
})
// 代理模式(ES6 proxy)
const star = {
name: '张三',
phone: '13000000000'
}
const agent = new Proxy(star, {
set(target, key, value, receiver) {
if (key === 'price') {
if (value < 100000) {
target[key] = 100000
} else {
target[key] = value
}
return true
}
},
get(target, key, receiver) {
if (key === 'phone') {
return '14000000000'
}
if (key === 'price' && !target[key]) {
return 120000
}
return target[key]
}
})
console.log('name', agent.name)
console.log('phone', agent.phone)
console.log('price砍价前', agent.price)
agent.price = 90000
console.log('price砍价后', agent.price)
设计原则:
代理类目标类分离,隔离开目标类和使用者
符合开放封闭原则
代理模式 vs 适配器模式
- 适配器模式:提供不同的接口
- 代理模式:提供一模一样的接口
代理模式 vs 装饰器模式
- 装饰器模式:扩展功能,原有功能不变且可直接使用
- 代理模式:显示原有功能,但是经过限制或者阉割之后
(九)外观模式
- 常用于处理需要兼容的情况
(十五)观察者模式
注:使用最多也是最重要的一种模式
- 发布订阅
- 一对多(一对一)
场景:网页事件绑定、Promise、jQuery callbacks、nodejs自定义事件(流的方式读取/写入文件、逐行读取文件流)等等
其他场景:nodejs中处理http请求、多进程通,vue和react组件生命周期,vue watch等等
// 观察者模式
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: ${this.subject.getState()}`)
}
}
const s = new Subject()
const o1 = new Observer('o1', s)
const o2 = new Observer('o2', s)
const o3 = new Observer('o3', s)
s.setState(1)
s.setState(2)
s.setState(3)
// 观察者模式(jQuery Callback)
const callback = $.Callbacks()
callback.add(function (info) {
console.log('fn1', info)
})
callback.add(function (info) {
console.log('fn2', info)
})
callback.add(function (info) {
console.log('fn3', info)
})
callback.fire('第一次触发')
// 观察者模式(node自定义事件,也可以被继承)
const EventEmitter = require('events').EventEmitter
const emitter = new EventEmitter()
// 监听 some 事件
emitter.on('some', data => {
console.log('fn1', data)
})
// 监听 some 事件
emitter.on('some', data => {
console.log('fn2', data)
})
// 触发 some 事件
emitter.emit('some', '触发')
// 观察者模式(node自定义事件,Stream)
const fs = require('fs')
const readStream = fs.createReadStream('./data/file.text')
let length = 0
readStream.on('data', data => {
length += data.toString().length
console.log('length', length)
})
readStream.on('data', data => {
console.log('len', data.toString().length)
})
readStream.on('end', data => {
console.log('length', length)
})
// 观察者模式(node自定义事件,readline)
const readline = require('readline')
const rl = readline.createInterface({
input: readStream
})
let lineNum = 0
rl.on('line', line => {
lineNum++
})
rl.on('close', () => {
console.log('lineNum', lineNum)
})
设计原则:
将目标和观察者分离,不是主动触发而是被动监听,两者解耦
符合开放封闭原则
(十六)迭代器模式
- 顺序访问一个集合(数组)
- 使用者无需知道集合的内部结构(封装)
场景:jQuery each、ES6 Iterator
ES6 Iterator为何存在:
1、ES6语法中,有序集合的数据已经有很多
2、Array、Map、Set、String、TypedArray、arguments、NodeList
3、需要有一个统一的遍历接口来遍历所有数据类型
4、(注意,object不是有序集合,可以 用Map代替)
ES6 Iterator是什么?
1、以上数据类型,都有 [Symbol.iterator] 属性
2、属性值是函数,执行函数返回一个迭代器
3、这个迭代器就有 next 方法可顺序迭代子元素
4、可运行 Array.prototype[Symbol.iterator] 来测试
ES6 Iterator 与 Generator
1、Iterator 的价值不限于上述几个类型的遍历
2、还有 Generator 函数的使用
3、即只要返回的数据符合Iterator接口的要求
4、即可使用Iterator语法,这就是迭代器模式
// 迭代器模式(jQuery each)
const arr = [1, 2, 3, 4]
const nodeList = document.getElementsByTagName('p')
const $p = $('p')
function each(data) {
const $data = $(data) // 生成迭代器
$data.each(function (key, item) {
console.log(key, item)
})
}
each(arr)
each(nodeList)
each($p)
// 迭代器模式(es6 自定义)
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 container = new Container([1, 2, 3, 4, 5, 6])
const iterator = container.getIterator()
while (iterator.hasNext()) {
console.log(iterator.next())
}
// 迭代器模式(es6 Iterator)
const it = {
each: function (data) {
const iterator = data[Symbol.iterator]()
let item = { done: false }
while (!item.done) {
item = iterator.next()
if (!item.done) {
console.log('value', item.value)
}
}
}
}
it.each('abcdef')
// for...of 是 iterator 的语法糖,我们不需要重复的去封装 each 方法
设计原则:
迭代器对象和目标对象分离
迭代器将使用者与目标对象隔离开
符合开放封闭原则
(二十)状态模式
- 一个对象有状态变化
- 每次状态变化都会触发一个逻辑
- 不能总是用 if…else 来控制
场景:有限状态机(javascript-state-machine)、Promise
// 状态模式
class State {
constructor(color) {
this.color = color
}
handle(context) {
console.log('turn to', this.color)
context.setState(this)
}
}
class Context {
constructor() {
this.state = null
}
getState() {
return this.state
}
setState(state) {
this.state = state
}
}
const context = new Context()
const green = new State('green')
const yellow = new State('yellow')
const red = new State('red')
green.handle(context)
console.log('现在是', context.getState())
yellow.handle(context)
console.log('现在是', context.getState())
red.handle(context)
console.log('现在是', context.getState())
// 状态模式(javascript-state-machine)
import StateMachine from 'javascript-state-machine'
const fms = new StateMachine({
init: '收藏', // 初始状态
transitions: [
{
name: 'collect', // 方法名
from: '收藏',
to: '取消收藏'
},
{
name: 'cancelCollect', // 方法名
from: '取消收藏',
to: '收藏'
}
],
methods: {
// 收藏,与 name 名一致,前面加on,驼峰命名
onCollect() {
console.log('收藏')
},
// 取消收藏
onCancelCollect() {
console.log('取消收藏')
}
}
})
const $btn = $('#btn')
// 更新文案
function updateBtnText() {
$btn.text(fms.state)
}
// 初始化文案
updateBtnText()
// 点击事件
$btn.on('click', function () {
if (fms.is('收藏')) {
fms.collect()
} else if (fms.is('取消收藏')) {
fms.cancelCollect()
}
updateBtnText()
})
// 状态模式(Promise)
const promiseFms = new StateMachine({
init: 'pending',
transitions: [
{
name: 'resolve',
form: 'pending',
to: 'fulfilled'
},
{
name: 'reject',
form: 'pending',
to: 'rejected'
}
],
methods: {
onResolve(state, data) {
data.resolveCallbacks.forEach(fn => fn())
},
onReject(state, data) {
data.rejectCallbacks.forEach(fn => fn())
}
}
})
class MyPromise {
constructor(fn) {
this.value = undefined
this.reason = undefined
this.resolveCallbacks = []
this.rejectCallbacks = []
const handleResolve = (value) => {
promiseFms.resolve(this)
}
const handleReject = (reason) => {
promiseFms.reject(this)
}
fn(handleResolve, handleReject)
}
then(fn1, fn2) {
this.resolveCallbacks.push(fn1)
this.rejectCallbacks.push(fn2)
}
}
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(true)
}, 1000)
})
myPromise.then(res => {
console.log(1)
})
myPromise.then(res => {
console.log(2)
})
设计原则:
将状态对象和主题对象分离,状态的变化逻辑单独处理
符合开放封闭原则