JavaScript常用设计模式与实践

一、引言

<<设计模式: 可复用面向对象软件的基础>> 一书中的定义是:

在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

这些模式并不是算法或者具体的实现。它们更像是想法、观点和抽象,辅助你去解决一些特定问题。

二、设计模式

1、单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

// 创建单例
class DatabaseConnection {
  constructor() {


    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance
    }
    // 初始化数据库连接
    this.connection = 'connection'
    DatabaseConnection.instance = this
  }





  getConnection() {
    return this.connection
  }

}


// 使用单例模式获取数据库连接
const dbConnection1 = new DatabaseConnection()
const dbConnection2 = new DatabaseConnection()



console.log(dbConnection1 === dbConnection2) // true
console.log(dbConnection1.getConnection()) // connection
console.log(dbConnection2.getConnection()) // connection

其实就是实例只会被创建一次,后续都是复用这一个实例

2、策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换

// 计算奖金
const strategies = {
  S: (salary) => {
    return salary * 4 // 4倍月工资作为奖金
  },
  A: (salary) => {
    return salary * 2 // 2倍
  },
  B: (salary) => {
    return salary * 1 // 1倍
  },
}





const calculateBonus = (level, salary) => {
  return strategies[level](salary)
}

calculateBonus('S', 10000) // 40000
calculateBonus('A', 800) // 16000

策略模式至少要由两部分组成:

  1. 封装了各类算法的策略类
  2. 接受参数的环境类,然后把请求委托给策略类进行处理

3、代理模式

访问对象时为一个对象提供一个代用品或者占位符,以便控制对它的访问

// proxy 示例

// 原始对象
const obj = {
  foo: 1,
}




// 代理后的对象
const newP = new Proxy(obj, {
  // 拦截读取
  get(target, key) {
    console.log(`访问了  ${key}!`)
    return target[key]
  },



  // 拦截设置属性操作
  set(target, key, value) {
    console.log(`设置了 ${key}!`)
    target[key] = value
  },
})



newP.foo // 访问了 foo!
newP.foo = 2 // 设置了 foo!

这里利用 ES6 的 Proxy 进行举例,可以发现我们不是直接操作原始的对象,而是对代理后的对象进行操作

Vue3中部分响应式原理就是利用了这个特性,如果想要更详细的了解可以访问我写的另一篇文章,这里不再赘述
vue3响应式方案

4、迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

// js 内置了迭代器
const arr = [1, 2, 3]
const iterator = arr[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}

其实Js还有 forEach、map等内置迭代器,这里就不再一一举例

5、发布订阅模式

又称观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知

// EventBus
const EventBus = (function () {
  const events = {}



  return {

    subscribe: function (eventName, callback) {
      if (!events[eventName]) {
        events[eventName] = []
      }


      events[eventName].push(callback)
    },





    publish: function (eventName, data) {
      if (!events[eventName]) {
        return
      }

      events[eventName].forEach((callback) => callback(data))
    },
  }

})()


// 订阅事件
EventBus.subscribe('eventA', (data) => {
  console.log(`Event A received: ${data}`)
})

// 发布事件
EventBus.publish('eventA', 'Hello, EventBus!') // Event A received: Hello, EventBus!

订阅者一旦订阅后,发布者发布事件都会通知到 每个订阅者,但是如果过度使用会导致难以跟踪维护,特别是多个发布者和订阅者嵌套的时候

6、命令模式

指一个执行某些特定事情的命令

// 宏命令 比如回家后通过语音助手执行到家命令
const createCommand = () => {
  return {
    commandsList: [],
    execute: () => {
      this.commandsList.forEach((command) => command.execute())
    },
    add: (command) => {
      this.commandsList.push(command)
    },
  }
}





const command1 = {
  execute: () => {
    console.log('开空调')
  },
}




const command2 = {
  execute: () => {
    console.log('开电视')
  },
}


const macroCommand = createCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.execute() // 开空调 开电视

命令模式的特点就是不知道请求的接收者是谁,也不知道被请求的操作是什么,所以需要借助命令对象来执行传入的命令

7、模板方法模式

模板方法是一种只需要使用继承就可以实现的非常简单的模式

// 每日晚餐模板
class Dinner {
  setup() {}



  start() {
    this.setup()
    this.cook() // 做饭
    this.eat() // 吃饭
    this.washDishes() // 洗碗
  }





  cook() {}
  eat() {}
  washDishes() {}
}




class ToDayDinner extends Dinner {
  setup() {
    console.log('今天吃青椒肉丝')
  }

  cook() {
    console.log('做青椒肉丝')
  }



  eat() {
    console.log('吃青椒肉丝')
  }


  washDishes() {
    console.log('洗碗')
  }
}

class TomorrowDinner extends Dinner {
  setup() {
    console.log('明天吃土豆肉丝')
  }

  cook() {
    console.log('做土豆肉丝')
  }

  eat() {
    console.log('吃土豆肉丝')
  }

  washDishes() {
    console.log('洗盘子')
  }
}


const todayDinner = new ToDayDinner()
todayDinner.start()

const tomorrowDinner = new TomorrowDinner()
tomorrowDinner.start()

这是一种典型通过封装来提高扩展性的设计模式

8、享元模式

运用共享技术来有效支持大量细粒度的对象

// 对象池工厂 也就是享元模式
const objectPoolFactory = function (createObjFn) {
  const objectPool = []



  return {

    create: function () {
      var obj = objectPool.length === 0 ? createObjFn.apply(this, arguments) : objectPool.shift()
      return obj
    },
    recover: function (obj) {
      objectPool.push(obj)
    },

  }

}




// 创建 DOM 工厂
const iframeFactory = objectPoolFactory(function () {
  const iframe = document.createElement('iframe')
  document.body.appendChild(iframe)


  iframe.onload = function () {
    iframe.onload = null
    iframeFactory.recover(iframe) // iframe加载完了回收节点
  }

  return iframe
})


const iframe1 = iframeFactory.create()
iframe1.src = 'https://www.baidu.com'


const iframe2 = iframeFactory.create()
iframe2.src = 'http://www.google.com/'

上面例子中需要反复创建 DOM,就比较适合使用享元模式来缓存对应的 DOM 对象,然后再添加到 DOM 对象上:

9、职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的偶合关系,将这些对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止

// 超市折扣  大于300 3折  大于200 5折  大于100 8折
class Discount {
  constructor() {


    this.discount = 0
    this.nextHandler = null
  }



  setNextHandler(handler) {
    this.nextHandler = handler
  }





  handleRequest(originPrice) {
    return originPrice * this.discount
  }

}




class DiscountHandler1 extends Discount {
  constructor() {
    super()
    this.discount = 0.3
  }




  handleRequest(originPrice) {
    if (originPrice >= 300) {
      return super.handleRequest(originPrice)
    }


    return this.nextHandler.handleRequest(originPrice)
  }
}

class DiscountHandler2 extends Discount {
  constructor() {
    super()
    this.discount = 0.5
  }


  handleRequest(originPrice) {
    if (originPrice >= 200) {
      return super.handleRequest(originPrice)
    }

    return this.nextHandler.handleRequest(originPrice)
  }
}

class DiscountHandler3 extends Discount {
  constructor() {
    super()
    this.discount = 0.8
  }


  handleRequest(originPrice) {
    if (originPrice >= 100) {
      return super.handleRequest(originPrice)
    }

    return this.nextHandler.handleRequest(originPrice)
  }
}

const discountHandler1 = new DiscountHandler1()
const discountHandler2 = new DiscountHandler2()
const discountHandler3 = new DiscountHandler3()

discountHandler1.setNextHandler(discountHandler2)
discountHandler2.setNextHandler(discountHandler3)

console.log(discountHandler1.handleRequest(250)) // 125
console.log(discountHandler1.handleRequest(150)) // 120

最大的优点就是可以解耦多个处理逻辑之间的复杂关系

10、装饰者模式

可以动态的给某个对象添加一些额外的职责,而不会影响从这个类派生的其他对象

// 原始对象
class Plane {
  fire() {
    console.log('发射普通子弹')
  }

}




// 装饰者给原始对象添加额外的职责
class AtomDecorator {
  constructor(plane) {
    this.plane = plane
  }




  fire() {
    this.plane.fire()
    console.log('发射原子弹')
  }
}




const plane = new Plane()
plane.fire() // 发射普通子弹



const atomDecorator = new AtomDecorator(plane)
atomDecorator.fire() // 发射普通子弹 发射原子弹

11、状态模式

区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变

// 事物内部的状态
class StateContent {
  constructor() {


    this.state = new StateA(this)
  }


  setState(state) {
    this.state = state
  }



  request() {
    this.state.handle()
  }

}




class State {
  constructor(context) {
    this.context = context
  }


  handle() {}
}


class StateA extends State {
  handle() {
    console.log('Change State to B')
    this.context.setState(new StateB(this.context))
  }

}


class StateB extends State {
  handle() {
    console.log('Change State to A')
    this.context.setState(new StateA(this.context))
  }
}


const context = new StateContent()
context.request() // Change State to B
context.request() // Change State to A
context.request() // Change State to B

状态模式定义了状态和行为之间的关系, 缺点是会在系统中定义许多状态类

12、适配器模式

解决两个软件实体间的接口不兼容

class OldPrinter {
  print(text) {
    console.log(`Old Printer: ${text}`)
  }
}
class NewPrinter {
  printFormatted(text) {
    console.log(`New Printer: ${text}`)
  }

}
// 适配器类
class PrinterAdapter {
  constructor(newPrinter) {
    this.newPrinter = newPrinter
  }
  print(text) {
    this.newPrinter.printFormatted(text)
  }
}
// 使用适配器模式
const oldPrinter = new OldPrinter()
const newPrinter = new NewPrinter()
const printerAdapter = new PrinterAdapter(newPrinter)
printerAdapter.print('Hello, Adapter Pattern!') // New Printer: Hello, Adapter Pattern!

主要就是解决两个已有接口之间不匹配的问题

三、结语

文本是最近阅读 《JavaScript设计模式与开发实践》 一书后,总结的部分

文中如有错误,欢迎大家在评论区指正,如果本文对你有帮助, 记得点赞?关注❤️

如需转载请标明作者和出处


© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYszI2Mc' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片