<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【4/25】在頁面對(duì)象中啟用模板方法模式(Template Method Pattern)

          共 4343字,需瀏覽 9分鐘

           ·

          2021-01-30 23:41

          這是《小游戲從0到1設(shè)計(jì)模式重構(gòu)》系列內(nèi)容第4篇,所有源碼及資料在“程序員LIYI”公號(hào)回復(fù)“小游戲從0到1”獲取。


          上一小節(jié)我們應(yīng)用了組合模式,對(duì)記分板對(duì)象Board進(jìn)行了容器改造,實(shí)際上在目前的小游戲項(xiàng)目中,容器絕不僅僅只有記分板,像游戲結(jié)束頁(GameOverPage)、游戲主頁(IndexPage)都應(yīng)該是容器對(duì)象。這一小節(jié)我們?cè)趹?yīng)用模板方法模式的同時(shí),進(jìn)一步應(yīng)用組合模式。


          首先看一下,在Game對(duì)象中,currentPage這個(gè)類變量統(tǒng)一代表GameOverPage和IndexPage,將在游戲運(yùn)行中依次調(diào)用:init、start、run、render、end。模板方法模式要求在父類中定義流程的總體框架,在子類中實(shí)現(xiàn)具體的邏輯。現(xiàn)在我們可以在GameOverPage與IndexPage的基類Page中,實(shí)現(xiàn)需要這些由Game調(diào)用的基本方法,然后在這兩個(gè)子頁面中提供具體的實(shí)現(xiàn)。


          前面我們提到,頁面對(duì)象本應(yīng)該是容器對(duì)象,在將頁面對(duì)象應(yīng)用模板方法模式時(shí),可以稍帶將它實(shí)現(xiàn)組合模式。先看一下Page類的改動(dòng):


          // page/page.js
          import Box from './box.js'
          class Page extends Box {
          constructor(){
          super()
          // let game = GameGlobal.game
          // game.on('touchMove', (e)=>{
          // // 僅在當(dāng)前頁傳遞事件
          // if (GameGlobal.game.currentPage == this){
          // this.touchMove.bind(this)(e)
          // }
          // })
          // game.on('touchEnd', (e)=>{
          // // 僅在當(dāng)前頁傳遞事件
          // if (GameGlobal.game.currentPage == this){
          // this.touchEnd.bind(this)(e)
          // }
          // })
          }
          /// 觸點(diǎn)移動(dòng)事件回調(diào)函數(shù)
          touchMove(e) {
          return (GameGlobal.game.currentPage == this)
          }
          /// 觸點(diǎn)結(jié)束事件回調(diào)函數(shù)
          touchEnd(e) {
          return (GameGlobal.game.currentPage == this)
          }
          init(options) { }
          start(){}
          run(){}
          // render(){}
          end(){}
          }

          export default Page


          在Page類中,我們使Page繼承于Box,使它成為一個(gè)容器,便于接下來在子類IndexPagek中添加子元素。還有,我們?cè)赑age類中添加start、run、end這些模板方法,render方法不需要添加了,因?yàn)樗贐ox中已經(jīng)有了。得益于js的不嚴(yán)謹(jǐn)性,我們?cè)赑age中以一種不一樣的返回值,重寫了touchMove、touchEnd這兩個(gè)方法,使其由不返回,改為返回布爾值。稍后我們?cè)谧宇愔袝?huì)看到這個(gè)重寫的作用。


          再看一個(gè)子類IndexPage:


          // page/index_page.js
          ...
          import Page from './page.js'
          /**
          * 主頁
          */
          class IndexPage extends Page {
          ...
          constructor() {
          super()
          }
          /// 初始化
          init(options) {
          ...
          this.addElement(this.bg)
          .addElement(this.leftPanel)
          .addElement(this.rightPanel)
          .addElement(this.ball)
          .addElement(this.systemBoard)
          .addElement(this.userBoard)
          .addElement(this.audioManager)
          }
          /// 渲染
          render() {
          // 清屏
          context.clearRect(0, 0, canvas.width, canvas.height)
          super.render()
          // // 背景
          // this.bg.render()
          // /// 繪制擋板
          // this.leftPanel.render()
          // this.rightPanel.render()
          // /// 繪制小球
          // this.ball.render()
          // /// 繪制分?jǐn)?shù)
          // this.systemBoard.render()
          // this.userBoard.render()
          // /// 調(diào)用音效管理者實(shí)例的渲染方法
          // this.audioManager.render()
          }
          /// 運(yùn)行
          run() {
          ...
          }
          /// 觸點(diǎn)移動(dòng)事件回調(diào)函數(shù)
          touchMove(e) {
          if (super.touchMove(e)){
          this.leftPanel.touchMove(e)
          }
          }
          /// 觸點(diǎn)結(jié)束事件回調(diào)函數(shù)
          touchEnd(e) {
          if (super.touchEnd(e)){
          this.audioManager.touchEnd(e)
          }
          }
          ...
          }

          module.exports = IndexPage


          我們看到,在IndexPage類的touchMove和touchEnd方法中,我們通過調(diào)用父類中的模板方法touchMove或touchEnd,獲知了當(dāng)前事件是否需要處理。這個(gè)地方充分體現(xiàn)了在模板方法模式中,父類中的方法完成的是一個(gè)模板,并不是一個(gè)完全需要被覆蓋的“虛函數(shù)”。(注:js中沒有虛函數(shù),虛函數(shù)是C++等高級(jí)語言中的概念。虛函數(shù)是面向?qū)ο缶幊讨袑?shí)現(xiàn)多態(tài)功能的一個(gè)重要組成成分,虛函數(shù)在父類中定義,在子類中被繼承和覆蓋。)


          我們?cè)倏匆幌翯ameOverPage的源碼:


          // page/game_over_page.js
          ...
          import Page from './page.js'
          /**
          * 游戲結(jié)束頁面
          */
          class GameOverPage extends Page {
          ...
          constructor() {
          super()
          }
          // init(options) { }
          ...
          render() {
          super.render()
          ...
          }
          /// 觸點(diǎn)移動(dòng)事件回調(diào)函數(shù)
          // touchMove(e) { }

          /// 觸點(diǎn)結(jié)束事件回調(diào)函數(shù)
          touchEnd(e) {
          if (super.touchEnd(e)){
          // 處理游戲結(jié)束單擊屏幕的邏輯
          this.audioManager.playHitAudio()
          game.start()
          }
          }
          // 開始
          // start() { }
          // 結(jié)束
          // end() { }
          }

          module.exports = GameOverPage


          應(yīng)用模板方法模式,對(duì)GameOverPage的代碼影響很小。


          在IndexPage類中,我們?cè)趇nit方法中通過父類的addElement方法添加了很多子元素:


          this.addElement(this.bg)
          .addElement(this.leftPanel)
          .addElement(this.rightPanel)
          .addElement(this.ball)
          .addElement(this.systemBoard)
          .addElement(this.userBoard)

          .addElement(this.audioManager)


          這些子元素都需要繼承于Component,以符合組合模式的要求。實(shí)現(xiàn)方法是類似的,僅舉Backgroud類做為示例看一下:


          // page/background.js
          import Component from './Component'
          /**
          * 背景對(duì)象
          */
          class Background extends Component {
          ...
          // constructor() { }
          ...
          }
          const background = Background.getInstance()

          module.exports = background


          基本上就是引入Component基類,然后繼承,其它代碼不需要修改。

          看一下運(yùn)行效果,和之前沒有什么區(qū)別:


          babc82a04be57db38a4ed0fda2552f25.webp

          最后總結(jié)一下,模板方法模式由兩部分結(jié)構(gòu)組成,一部分是抽象父類,另一部分是具體的子類。父類負(fù)責(zé)封裝固定流程,子類負(fù)責(zé)實(shí)現(xiàn)具體邏輯。在這一小節(jié)的重構(gòu)中,Page是模板方法模式中的父類,IndexPage與GameOverPage是模板中的子類。init、start、run、render和end這些方法,是在Game類中調(diào)用的模板方法,它們?cè)赑age類中定義,在IndexPage與GameOverPage這兩個(gè)子類中有各自的重寫實(shí)現(xiàn)。touchMove和touchEnd方法,不是Page類定義的,但它們也可以算作模板方法的一部分,并且充分體現(xiàn)了模板方法作為模板的意義,而不僅僅是作為一個(gè)父類中被重寫的方法符號(hào)。


          在ES6語法中有一個(gè)叫做模板字符串的語法 ,它可以看作是模板方法模式在字符串操作上的具體運(yùn)用。看一個(gè)示例:


          let s = "我是${ly},來自${location}。"


          在這個(gè)字符串中,ly與location是變量,通過${}這樣的語法內(nèi)嵌于字符串中。整個(gè)字符串文本可以看作是一個(gè)模板父本,而內(nèi)嵌的變量可以看作是重寫的子元素。模板字符串內(nèi)在的實(shí)現(xiàn)思想與模板方法模式是相似的,我們?cè)陂_發(fā)中也可以學(xué)其應(yīng)用的靈活性,不必拘泥于父子類的形式。


          階段源碼


          本小節(jié)階段源碼見:disc/第五章/5.1.4。


          我講明白沒有,歡迎提問。


          2021年1月30日


          本文頻:


          瀏覽 122
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  2016超碰 | 黄色视频网站国产 | 色婷婷综合在线观看 | 91麻豆精品国产91久久久ios版 | 强伦轩一区二区三区四区 |