前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【5/25】责任链模式(Chain of Responsibility Pattern)

【5/25】责任链模式(Chain of Responsibility Pattern)

作者头像
LIYI
发布2021-02-23 16:05:56
7320
发布2021-02-23 16:05:56
举报
文章被收录于专栏:艺述论专栏艺述论专栏

这是《小游戏从0到1设计模式重构》系列内容第5篇,所有源码及资料在“程序员LIYI”公号回复“小游戏从0到1”获取。

这一小节我们尝试应用责任链模式。

责任链模式的定义是,当软件中一个处理请求产生时,使多个对象都有机会处理该请求,避免请求的发送者和接收者之间直接的耦合关系。责任链模式将这些对象连成一条链条,并沿着这条链传递该请求,直到有一个对象处理它为止。

在我们的小游戏中,有播放单击音频的需求,在小球撞击左右挡板时、游戏结束单击屏幕时以及确认游戏重开单击【确认】按钮时都需要播放这个音效。目前我们是通过分别在IndexPage、GameOverPage中引入一个AudioManager的单例对象完成这个工作的。但这个实现并不好,如果我们添加新的页面,还需要再次引入AudioManager单例,并且页面直接调用了playHitAudio方法,代码耦合性太强,不利于代码的维护。

接下来我们准备定义一个播放音效的任务对象Task,让它继承于Event类,当这个任务产生时在Game对象上派发一个名称为“playAudio”的事件。谁有能力处理这个任务,谁就监听并处理这个事件。我们看一下相关代码如何实现,先看一下Task类:

代码语言:javascript
复制
// lib/task.js
import Event from './event'

class Task extends Event{
  constructor(name){
    super()
    this.name = name 
    this.isDone = false 
  }
  // 发送这个任务
  send(){
    GameGlobal.game.emit(this.name, this)
  }
}
export default Task

在这个Task类中,name是任务名称,也是事件名称,isDone代表任务是否完成。send方法在任务开始时将在全局的Game实例上派发事件,并将自身作为事件的参数。为什么将Task自身作为事件参数,稍后就会看到。

Task类是一个相对通用的任务类,接下来我们再实现一个具体的播放单击音效的任务类PlayAudioTask:

代码语言:javascript
复制
// lib/play_audio_task.js
import Task from './task'

class PlayAudioTask extends Task {
  constructor(audioName = 'hit'){
    super('playAudio')
    this.audioName = audioName
  }
}
export default PlayAudioTask

这个具体的子类只是添加了一个audioName属性,我们再看一下AudioManager类如何修改:

代码语言:javascript
复制
// manager/audio_manager.js
...
class AudioManager extends Component {
  ...
  init(options) {
    ...
    GameGlobal.game.on('playAudio', task=>{
      switch(task.audioName){
        case 'hit':
        default:
          if (!task.isDone) {
            task.isDone = true 
            this.playHitAudio()
          }
      }
    })
  }
  ...
}

既然AudioManager可以处理单击音效的播放任务,又可以访问到全局Game实例,所以就在它的init方法中,添加对“playAudio”事件的监听和处理逻辑。在这里我们看到了isDone属性的作用,如果任务已经完成了,就不需要重复处理了。

如果AudioManager实例无法处理"playAudio"这个任务,而只是可以访问到Game实例,那么它也可以将这个事件继续派发给它知道的、有可能处理的子对象,这个责任的传递,就是责任链模式的意义。当然我们这个项目太小,不存在复杂的子系统及子子系统,所以责任链的传播路径看起来非常短。

最后再看一下如何使用改造后的责任链任务对象,仅看一个有代表性的GameOverPage中的示例:

代码语言:javascript
复制
// page/game_over_page.js
...
import PlayAudioTask from '../lib/play_audio_task'
/**
 * 游戏结束页面
 */
class GameOverPage extends Page {
  ...
  /// 触点结束事件回调函数
  touchEnd(e) {
    if (super.touchEnd(e)){
      // 处理游戏结束单击屏幕的逻辑
      // this.audioManager.playHitAudio()
      new PlayAudioTask().send()
      game.start()
    }
  }
  ...
}

在IndexPage类中的使用与之相似。

小游戏的运行效果与之前没有差异:

最后总结一下,在这一小节我们通过创建任务对象Task及子类PlayAudioTask,还有对AudioManager类的改造,完成了一个微型责任链模式的实现。在任务对象Task中,我们特意添加了一个isDone属性,谁处理了这个任务,谁就将这个属性设置为true,这样就避免了重复处理。得益于js是一门简单的单线程语言,我们根本不需要考虑多线程争抢改变一个任务对象的极端情况。

阶段源码

本小节阶段源码见:disc/第五章/5.1.5。

我讲明白没有,欢迎提问。

2021年1月30日

本文视频:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 艺述论 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 阶段源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档