前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【2/25】在Game上应用观察者模式(Observer Pattern)

【2/25】在Game上应用观察者模式(Observer Pattern)

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

作者使用过的最简洁的观察者模式,就是AS3源码里的EventDispatcher事件派发者对象。任何继承于这个类的对象,都可以间接实现观察者模式。

我们可以先实现一个Event类,这个代码稍后会用到:

代码语言:javascript
复制
// lib/event.js
class Event{
  cache 
  constructor(){
    this.cache = {}
  }
  // 监听
  on(eventType, func){
    // this.cache[eventType] = func
    (this.cache[eventType] || (this.cache[eventType] = [])).push(func)
  }
  // 移除监听
  off(eventType, func){
    if (func){
      let stack = this.cache[eventType]
      if (stack && stack.length > 0){
        for(let j=0;j<stack.length;j++){
          if (stack[j] == func){
            stack.splice(j, 1)
            break
          } 
        }
      }
    }else{
      delete this.cache[eventType]
    }
  }
  // 监听一次
  once(eventType, func){
    function on(){
      this.off(eventType, on)
      func.apply(this, arguments)
    }
    this.on(eventType, on)
  }
  // 发布订阅通知
  emit(eventType, ...args){
    const stack = this.cache[eventType]
    if (stack && stack.length > 0){
      stack.forEach(item => item.apply(this, args))
    }
  }
}
export default Event

在这个Event中,方法on是添加一个监听,off是移除监听,once是添加只能执行一次的监听,emit是发射事件。Event类将观察者模式中的订阅和通知行为,在一个对象中统一定义了。通知内容是不重名的eventType,这是一个字符串类型。

在Game.js文件中,目前有这样的代码:

代码语言:javascript
复制
/// 初始化
init() {
  ...
  /// 监听触点移动事件
  wx.onTouchMove(this.touchMove.bind(this))
  /// 监听触点结束事件
  wx.onTouchEnd(this.touchEnd.bind(this))
  ...
}
/// 触点移动事件回调函数
touchMove(e) {
  this.currentPage.touchMove(e)
}
/// 触点结束事件回调函数
touchEnd(e) {
  this.currentPage.touchEnd(e)
}

我们看一下,目前我们对触点移动事件和触点结束事件的监听,是在init方法中通过wx.onTouchMove和wx.onTouchEnd完成的。事件的传递,是通过调用currentPage对象的touchMove方法和touchEnd方法完成的。

这种方式耦合太强了。我们在Game.js监听全局触点事件然后主动去通知页面,就像在一个村镇里邮差挨家挨户去送报纸信件一样,很麻烦也很低效。现在我们转换一个思路,小镇邮差使用村镇大喇叭广播,“取报了,报报了”,让订报的人主动来找邮差取。这种方式如果不违反邮局规定,应该比前一种方式更好。

接下来我们让Game类继承于Event,让Game在触点移动和触点结束时分别派发“touchMove”和“touchEnd”事件,然后让游戏中的两个页面(IndexPage和GameOverPage)自己监听这两个监听事件并处理,如果它们需要的话。

看代码:

代码语言:javascript
复制
// game.js
const Event = require('./lib/event')
class Game extends Event {
  ...
  touchMove(e) {
    // this.currentPage.touchMove(e)
    this.emit('touchMove', e)
  }
  touchEnd(e) {
    // this.currentPage.touchEnd(e)
    this.emit('touchEnd', e)
  }
  ...
}

因为IndexPage和GameOverPage这两个页面拥有相同的touchMove和touchEnd方法,所以我们可以创建一个名称为Page的基类,然后让这两个页面继承于它:

代码语言:javascript
复制
// page/page.js
class Page {
  constructor(){
    let game = GameGlobal.game
    game.on('touchMove', (e)=>{
      // 仅在当前页传递事件
      if (game.currentPage == this){
        this.touchMove.bind(this)(e)
      }
    })
    game.on('touchEnd', (e)=>{
      if (game.currentPage == this){
        this.touchEnd.bind(this)(e)
      }
    })
  }
  /// 触点移动事件回调函数
  touchMove(e) {}
  /// 触点结束事件回调函数
  touchEnd(e) {}
}


export default Page


// page/game_over_page.js
import Page from './page.js'
class GameOverPage extends Page {
  ...
  constructor() { 
    super()
  }
  /// 触点移动事件回调函数
  // touchMove(e) { }
  ...
}


// page/index_page.js
import Page from './page.js'
class IndexPage extends Page {
  ...
  constructor() { 
    super()
  }

我们在Page类中,通过全局变量GameGlobal.game获取了游戏实例,然后监听它的触点事件。我们注意到,这里有一个if判断,只有当前页是游戏激活的游戏页面(currentPage)时,才会触发处触点事件的处理。

在GameOverPage和IndexPage子类中,可以通过同类方法覆盖父类方法。在GameOverPage类中,因为不需要知道touchMove事件,所以它的touchMove方法就不需要重写了。

我们注意到,在game.js文件中我们对上一小节实现的单例代码进行了一点小的修改:

代码语言:javascript
复制
class Game{
  ...
  // gameOverPage = new GameOverPage()
  gameOverPage //游戏结束页面
  // indexPage = new IndexPage()
  indexPage //主页
  ...
  constructor() { 
    super()
    GameGlobal.game = this
    this.gameOverPage = new GameOverPage()//游戏结束页面
    this.indexPage = new IndexPage()//主页
  }
}
...
// GameGlobal.game = game
game.init()
game.start()

我们将全局游戏实例的设置,放在了构造器中,并且将gameOverPage和indexPage这两个页面对象变量的实例化,放在构造器中。之所以这样修改,是因为我们需要在两个页面基类的构造器中访问全局的游戏实例,我们必须保证在此之前它已经被设置。

现在我们已经在Game对象应用了观察者模式,游戏的运行效果与之前是一样的:

阶段源码

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

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

2021年1月26日

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

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

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

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

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