1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【4/25】在頁(yè)面對(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é)束頁(yè)(GameOverPage)、游戲主頁(yè)(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è)子頁(yè)面中提供具體的實(shí)現(xiàn)。


        前面我們提到,頁(yè)面對(duì)象本應(yīng)該是容器對(duì)象,在將頁(yè)面對(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)前頁(yè)傳遞事件
        // if (GameGlobal.game.currentPage == this){
        // this.touchMove.bind(this)(e)
        // }
        // })
        // game.on('touchEnd', (e)=>{
        // // 僅在當(dāng)前頁(yè)傳遞事件
        // 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è)容器,便于接下來(lái)在子類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'
        /**
        * 主頁(yè)
        */
        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方法中,我們通過(guò)調(diào)用父類中的模板方法touchMove或touchEnd,獲知了當(dāng)前事件是否需要處理。這個(gè)地方充分體現(xiàn)了在模板方法模式中,父類中的方法完成的是一個(gè)模板,并不是一個(gè)完全需要被覆蓋的“虛函數(shù)”。(注:js中沒有虛函數(shù),虛函數(shù)是C++等高級(jí)語(yǔ)言中的概念。虛函數(shù)是面向?qū)ο缶幊讨袑?shí)現(xiàn)多態(tài)功能的一個(gè)重要組成成分,虛函數(shù)在父類中定義,在子類中被繼承和覆蓋。)


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


        // page/game_over_page.js
        ...
        import Page from './page.js'
        /**
        * 游戲結(jié)束頁(yè)面
        */
        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方法中通過(guò)父類的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語(yǔ)法中有一個(gè)叫做模板字符串的語(yǔ)法 ,它可以看作是模板方法模式在字符串操作上的具體運(yùn)用??匆粋€(gè)示例:


        let s = "我是${ly},來(lái)自${location}。"


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


        階段源碼


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


        我講明白沒有,歡迎提問(wèn)。


        2021年1月30日


        本文頻:


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人免费在线观看 | 白丝嫩逼| 欧美视频一区二区三区 | 欧美三级,美国一级 | 狠狠操狠狠操狠狠操 | 亚洲女人被黑人巨大的原因 | 女性高爱潮视频 | 蜜桃在线码无精品秘 入口九色 | 4人痴女超短裙ol波多野结衣 | 国产老师做www爽爽爽视频 |