这是《小游戏从0到1设计模式重构》系列内容第5篇,所有源码及资料在“程序员LIYI”公号回复“小游戏从0到1”获取。
这一小节我们尝试应用责任链模式。
责任链模式的定义是,当软件中一个处理请求产生时,使多个对象都有机会处理该请求,避免请求的发送者和接收者之间直接的耦合关系。责任链模式将这些对象连成一条链条,并沿着这条链传递该请求,直到有一个对象处理它为止。
在我们的小游戏中,有播放单击音频的需求,在小球撞击左右挡板时、游戏结束单击屏幕时以及确认游戏重开单击【确认】按钮时都需要播放这个音效。目前我们是通过分别在IndexPage、GameOverPage中引入一个AudioManager的单例对象完成这个工作的。但这个实现并不好,如果我们添加新的页面,还需要再次引入AudioManager单例,并且页面直接调用了playHitAudio方法,代码耦合性太强,不利于代码的维护。
接下来我们准备定义一个播放音效的任务对象Task,让它继承于Event类,当这个任务产生时在Game对象上派发一个名称为“playAudio”的事件。谁有能力处理这个任务,谁就监听并处理这个事件。我们看一下相关代码如何实现,先看一下Task类:
// 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:
// 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类如何修改:
// 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中的示例:
// 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日
本文视频: