JavaScript设计模式学习笔记二

213 阅读5分钟

模板方法模式

定义一个超类,超类中定义一系列方法,定义了一个模板————封装了完成一个功能、行为的步骤流程,子类根据需要重载超类方法,但是会调用超类模板方法,按照模板规定行为运行

案例1

/*
 *定义一个游戏运行模板,负责运行各种游戏
 */
class Game {
    register() {
        console.log('角色注册')
    }
    initialize() {
        console.log('初始化游戏')
    }
    start() {
        console.log('开始玩游戏')
    }
    end() {
        console.log('结束游戏')
    }
    //模板方法-抽象出不变的流程步骤,每步具体操作可变
    play() {
        this.register()
        this.initialize()
        this.start()
        this.end()
    }
}

class Card extends Game {
    constructor(name) {
        super(name)
        this.name = name
    }
    register() {
        console.log(`${this.name} - 注册成功`)
    }
    initialize() {
       console.log(`${this.name} - 游戏开始初始化`) 
    }
    start() {
        console.log(`${this.name} - 游戏已经开始`)
    }
    end() {
        console.log(`${this.name} - 游戏已经结束`)
    }
}

class Ball extends Game {
    constructor(name) {
        super(name)
        this.name = name
    }
    register() {
        console.log(`${this.name} - 注册成功`)
    }
    initialize() {
       console.log(`${this.name} - 足球游戏开始初始化`) 
    }
    start() {
        console.log(`${this.name} - 足球游戏已经开始`)
    }
    end() {
        console.log(`${this.name} - 足球游戏已经结束`)
    }
}

const cardGame = new Card('卡牌游戏')
cardGame.play()

const ballGame = new Ball('球类游戏')
ballGame.play()

钩子方法

在具体场景应用时,有时需要需要模板方法中封装的流程可以定制化,这时可以使用钩子方法

class Game {
    register() {
        console.log('角色注册')
    }
    //执行注册开关
    registerSwitch() {
        return true
    }
    initialize() {
        console.log('初始化游戏')
    }
    start() {
        console.log('开始玩游戏')
    }
    end() {
        console.log('结束游戏')
    }

    //模板方法-抽象出不变的流程步骤,每步具体操作可变
    play() {
        if (this.registerSwitch()) {
            this.register()
        }
        this.initialize()
        this.start()
        this.end()
    }
}
//Football游戏不需要注册就可以玩
class Football extends Game {
    constructor(name, type) {
        super(name)
        this.name = name
        this.type = type    //游戏是否收费 0:收费 1:免费
    }
    register() {
        console.log(`${this.name} - 注册成功`)
    }
    registerSwitch() {
        let _opt = {
            0: () => {
                return true
            },
            1: () => {
                return false
            }
        }
        return _opt[this.type]()
    }
    initialize() {
       console.log(`${this.name} - 足球游戏开始初始化`) 
    }
    start() {
        console.log(`${this.name} - 足球游戏已经开始`)
    }
    end() {
        console.log(`${this.name} - 足球游戏已经结束`)
    }
}
const fb0 = new Football('football0', 0)
const fb1 = new Football('football1', 1)
fb0.play()  //需要注册
fb1.play()  //不需要注册

享元模式

通过减少对象创建数量,以减少内存占用和提高性能。

具体通过分离出内部状态和外部状态来实现

  • 内部状态存储在对象内部,决定创建对象个数,内部状态可以看做对象在内存中的标识符,没有时创建存在时直接返回对象
  • 外部状态和应用场景相关,在需要时传入对象中,可以看做具体业务时的需要的外部数据

案例

现在需要绘制1000个图形,其中方形、圆形、椭圆形、三角形各250个,每种类型的颜色、大小均随机

正常实现: 创建了1000个对象去执行绘制操作

const DrawTools = {
    //方形
    square: function draw(params) {
        console.log(`方形 - w:${params.w} - h:${params.h} - 
        颜色:${params.color}`)
    },
    //圆形
    circle: function draw(params) {
        console.log(`圆形 - 半径:${params.radius} - 
        颜色:${params.color}`)
    },
    //椭圆形
    ellipse: function draw(params) {
        console.log(`椭圆形 - a:${params.a} - b:${params.b}- 
        颜色:${params.color}`)
    },
    //三角形
    triangle: function draw(params) {
        console.log(`三角形 - x:${params.x} - 
        y:${params.y} - z:${params.z} - 
        颜色:${params.color}`)
    }
}

function Graphics(type) {
    this.type = type    //对象初始化时存储,内部状态
}

Graphics.prototype.draw = function(operator) {
    operator[this.type](this.params)
}

Graphics.prototype.setParams = function (params) {
    this.params = params    //外部状态,具体业务场景时在传入对象的数据
}

function drawDemo(num = 1000) {
    let startT = +new Date
    let aver = num / 4
    let color = ['blue', 'green', 'yellow', 'white']
    for (let i = 1; i <= num; i++) {
        if (i <= aver) {    //画方形
            let wh = [[10, 2], [2, 4], [7,8]][Math.floor((Math.random()*3))]
            let square = new Graphics('square')
            //具体场景数据,不同尺寸、颜色
            square.setParams({
                w: wh[0],
                h: wh[1],
                color: color[Math.floor(Math.random()*color.length)]
            })
            square.draw(DrawTools)
        }else if (i <= 2 * aver) {    //圆形
            let radius = [1, 2, 3, 5, 9]
            let circle = new Graphics('circle')
            circle.setParams({
                radius: radius[Math.floor(Math.random()*radius.length)],
                color: color[Math.floor(Math.random()*color.length)]
            })
            circle.draw(DrawTools)
        }else if (i <= 3 * aver) {    //椭圆形
            let ab = [[1, 2], [3, 4], [6, 8]][Math.floor((Math.random()*3))]
            let ellipse = new Graphics('ellipse')
            ellipse.setParams({
                a: ab[0],
                b: ab[1],
                color: color[Math.floor(Math.random()*color.length)]
            })
            ellipse.draw(DrawTools)
        }else if(i <= 4 * aver) { //三角形
            let xyz = [[6, 7, 8], [3, 4, 5], [2, 1, 2]]
            let triangle = new Graphics('triangle')
            triangle.setParams({
                x: xyz[0],
                y: xyz[1],
                z: xyz[2],
                color: color[Math.floor(Math.random()*color.length)]
            })
            triangle.draw(DrawTools)
        }
    }
    console.log(`用时 - ${new Date - startT}ms`)
}
drawDemo()

享元模式实现:创建了4个对象完成绘制

  • 对象通过工厂函数创建
  • 外部状态在业务需要时,通过特定方法传入对象内部
const DrawTools = {
    //方形
    square: function draw(params) {
        console.log(`方形 - w:${params.w} - h:${params.h} - 
        颜色:${params.color}`)
    },
    //圆形
    circle: function draw(params) {
        console.log(`圆形 - 半径:${params.radius} - 
        颜色:${params.color}`)
    },
    //椭圆形
    ellipse: function draw(params) {
        console.log(`椭圆形 - a:${params.a} - b:${params.b}- 
        颜色:${params.color}`)
    },
    //三角形
    triangle: function draw(params) {
        console.log(`三角形 - x:${params.x} - 
        y:${params.y} - z:${params.z} - 
        颜色:${params.color}`)
    }
}

function Graphics(type) {
    this.type = type    //内部状态
}

Graphics.prototype.draw = function(operator) {
    operator[this.type](this.params)
}

Graphics.prototype.setParams = function (params) {
    this.params = params    //外部状态
}

//工厂函数,内部状态决定对象个数
var GraphicsFactory = (function () {
    let _objCache = {}
    return {
        create(type) {
            return _objCache[type] ? _objCache[type] 
                : (_objCache[type] = new Graphics(type))
        },
        getObjCache() {
            return _objCache
        }
    }
})()

function drawDemo(num = 1000) {
    let startT = +new Date
    let aver = num / 4
    let color = ['blue', 'green', 'yellow', 'white']
    for (let i = 1; i <= num; i++) {
        if (i <= aver) {    //画方形
            let wh = [[10, 2], [2, 4], [7,8]][Math.floor((Math.random()*3))]
            let square = GraphicsFactory.create('square')
            //具体场景数据,不同尺寸、颜色
            square.setParams({
                w: wh[0],
                h: wh[1],
                color: color[Math.floor(Math.random()*color.length)]
            })
            square.draw(DrawTools)
        }else if (i <= 2 * aver) {    //圆形
            let radius = [1, 2, 3, 5, 9]
            let circle = GraphicsFactory.create('circle')
            circle.setParams({
                radius: radius[Math.floor(Math.random()*radius.length)],
                color: color[Math.floor(Math.random()*color.length)]
            })
            circle.draw(DrawTools)
        }else if (i <= 3 * aver) {    //椭圆形
            let ab = [[1, 2], [3, 4], [6, 8]][Math.floor((Math.random()*3))]
            let ellipse = GraphicsFactory.create('ellipse')
            ellipse.setParams({
                a: ab[0],
                b: ab[1],
                color: color[Math.floor(Math.random()*color.length)]
            })
            ellipse.draw(DrawTools)
        }else if(i <= 4 * aver) { //三角形
            let xyz = [[6, 7, 8], [3, 4, 5], [2, 1, 2]]
            let triangle = GraphicsFactory.create('triangle')
            triangle.setParams({
                x: xyz[0],
                y: xyz[1],
                z: xyz[2],
                color: color[Math.floor(Math.random()*color.length)]
            })
            triangle.draw(DrawTools)
        }
    }
    console.log(`用时 - ${new Date - startT}ms`)
}
drawDemo()

对象池

同享元模式相似,都是性能优化方案,不过不用分离内部状态和外部状态

对象池维护一个装载空闲对象的内存池,当需要对象完成业务时直接从对象池获取,否则创建新的对象,任务完成后将对象回收到对象池

常见应用如http连接池、数据库连接池,在web中常用来缓存DOM对象,减少DOM节点创建和删除

//页面显示5张图,一段时间后更换
function objectPoolFactory(fn) {
    let _objectPool = []
    return {
        create() {
            let obj = _objectPool.length == 0 ? fn.apply(null, arguments) 
                : _objectPool.shift()
            return obj
        },
        recycle(obj) {
            _objectPool.push(obj)
        }
    }
}

var ImgFactory = objectPoolFactory(() => {
    let img = document.createElement('img')
    document.body.appendChild(img)
    img.onload = () => {
        ImgFactory.recycle(img) //图片加载完成后回收对象
    }
    return img
})

for (let i = 0; i < 5; i++) {
    var img = ImgFactory.create()
    img.src = 'xx'+ i + '.jpg'
}

setTimeout(() => {
    for (let i = 0; i < 5; i++) {
        var img = ImgFactory.create()
        img.src = 'yy'+ i + '.jpg'
    }
}, 5000)

职责链模式

创建了一个处理请求的对象链,请求会沿着链依次传递直到被处理;请求发送者不需要关心接受者,只需将请求发送到链的第一个节点就行,请求发送者和接受者之间进行了解耦

示例:打印日志信息,其中每个日志模块只能打印级别不低于自己的信息,否者交给更低级别模块打印

//打印处理模块
const LogTools = {
    error: {
        level: 3,
        log(message) {
            console.error(`error - ${message}`)
        }
    },
    warn: {
        level: 2,
        log(message) {
            console.warn(`warn - ${message}`)
        }
    },
    info: {
        level: 1,
        log(message) {
            console.log(`info - ${message}`)
        }
    }
}

//职责链中节点类
function LogChain(logTool) {
    this.level = logTool.level  //节点处理日志级别
    this.log = logTool.log  //处理逻辑
    this._nextChainNode = null    //下个节点
}

LogChain.prototype.setNextChainNode = function(chain) {
    this._nextChainNode = chain
}

//节点处理请求或是转发请求
LogChain.prototype.handleRequest = function(level, msg) {
    'use strict';
    if (this.level <= level) {
        //非严格模式下,arguments和函数参数变量引用关联,此时msg会覆盖level;严格模式下非引用关联
        [].shift.apply(arguments)
        this.log.apply(this, arguments)
    }
    if (this._nextChainNode) {
        //请求传递
        this._nextChainNode.handleRequest(level, msg)
    }
}

function chainDemo() {
    let chain = _getChain()
    chain.handleRequest(1, 'info log message.')
    chain.handleRequest(2, 'warn log message.')
    chain.handleRequest(3, 'error log message.')

    //获取职责链
    function _getChain() {
        //在业务需要时进行节点组合,业务和功能模块解耦
        let errorChain = new LogChain(LogTools.error)
        let warnChain = new LogChain(LogTools.warn)
        let infoChain = new LogChain(LogTools.info)
        errorChain.setNextChainNode(warnChain)
        warnChain.setNextChainNode(infoChain)
        return errorChain
    }
}
chainDemo()
// info - info log message.
// warn - warn log message.
// info - warn log message.
// error - error log message.
// warn - error log message.
// info - error log message.

AOP实现职责链

结合JavaScript函数式编程的特点,可以很灵活地实现一个职责链,缺点是闭包保存了各级函数的作用域,当链较长时会导致性能问题

//待续