这是《小游戏从0到1设计模式重构》系列内容第9篇,所有源码及资料在“程序员LIYI”公众号回复“小游戏从0到1”获取。
建造者模式是将一类复杂产品的建造过程,以一定的顺序分解成很多子步骤。每个具体产品的建造,会遵循同样的流程,但因为每一个步骤的具体实现不尽相同,因此构建出的产品呈现出不同的表象和行为。
在我们目前的小游戏项目中,最有可能应用建造者模式的是页面对象。两个页面GameOverPage和IndexPage它们拥有的页面元素不同,正好适用建造者模式统一流程下的不同子步骤进行构建。
建造者模式主要有四个部分:
在这一小节中,产品类是页面对象,已经有了,不需要再创建;需要创建的是Builder建造者类和Director建造指挥者类。
我们将游戏页面的创建,分为前景、中景和背景三个部分,这是我们创建页面的子步骤。首先我们创建一个建造者基类PageBuilder,这个基类是作为接下来两个具体的子类的父类而存在的,在这个基类里面,我们要定义创建页面对象的基本步骤。我们看一下它的代码:
// page/page_builder.js
class PageBuilder{
page
constructor(page){
this.page = page
}
// 创建背景
buildBackground(){}
// 创建中景
buildGameElements(){}
// 构建前景
buildForeground(){}
// 创建背景对象
buildBgObject(){
this.page.bg = require("./background.js")
this.page.addElement(this.page.bg)
}
// 创建音频管理者对象
buildAudioManager(){
// 音频管理者
this.page.audioManager = require("../manager/audio_manager.js")
// 初始化音效管理者
this.page.audioManager.init({
musicButtonOnImageUrl: "http://xiuxing-1252822131.cossh.myqcloud.com/book/sound-on.jpg",
musicButtonOffImageUrl: "http://xiuxing-1252822131.cossh.myqcloud.com/book/sound-off.jpg",
bgAudioUrl: "https://opengameart.org/sites/default/files/audio_preview/01%20track%201.ogg.mp3"
})
this.page.addElement(this.page.audioManager)
}
// 返回产品对象
getPage(){
return this.page
}
}
export default PageBuilder
在这个建造者类中,我们有三个基本的构建方法:buildBackground负责构建背景、buildGameElements负责构建中景游戏元素、buildForeground负责构建前景。但这三个方法只是“虚”方法,具体的实现要在子类中完成。
为了让子类方便复用代码,我们可以将有两个子页面都调用的代码,抽离为特殊的对象构建方法,放在父类中。例如,buildAudioManager负责构建音频管理者对象,buildBgObject这个方法负责构建背景对象。这些方法的具体创建代码,以前已经写过了,我们只是把它们从一个地方拷贝到另一个地方。
接下来我们创建一个具体的建造者类IndexPageBuilder,负责构建游戏主页:
// page/index_page_builder.js
import PageBuilder from './page_builder'
class IndexPageBuilder extends PageBuilder{
// 创建背景
buildBackground(){
this.buildBgObject()
}
// 创建中景
buildGameElements(){
// 球
this.page.ball = require("./ball")
// 球的初始化
this.page.ball.init({})
this.page.addElement(this.page.ball)
// 挡板对象
this.page.leftPanel = require("./left_panel.js")
this.page.rightPanel = require("./right_panel.js")
// 挡板初始化
this.page.leftPanel.init({})
this.page.rightPanel.init({})
this.page.addElement(this.page.leftPanel)
this.page.addElement(this.page.rightPanel)
// 记分板对象
this.page.userBoard = require("./user_board.js")
this.page.systemBoard = require("./system_board.js")
// 检查用户授权情况,拉取用户头像并准备绘制
this.page.userBoard.init({})
this.page.addElement(this.page.userBoard)
this.page.addElement(this.page.systemBoard)
}
// 构建前景
buildForeground(){
this.buildAudioManager()
}
}
export default IndexPageBuilder
在这个具体的子类中,重写了父类中三个基本的“虚”方法。
接下来再创建一个游戏结束页面建造者类GameOverPageBuilder:
// page/game_over_page_builder.js
import PageBuilder from './page_builder'
class GameOverPageBuilder extends PageBuilder{
// 构建前景
buildForeground(){
this.buildAudioManager()
}
}
export default GameOverPageBuilder
游戏结束页面不像游戏主页那样复杂,只重写一个构建前景的“虚”方法就可以了。
最后出场的是建造指挥者类PageBuildDirector:
// page/page_build_director.js
import IndexPage from './index_page'
import GameOverPage from './game_over_page'
import IndexPageBuilder from './index_page_builder'
import GameOverPageBuilder from './game_over_page_builder'
class PageBuildDirector{
// 构建页面
static buildPage(pageName){
let page,builder
switch (pageName) {
case 'index':
page = new IndexPage()
builder = new IndexPageBuilder(page)
break;
case 'gameOver':
default:
page = new GameOverPage()
builder = new GameOverPageBuilder(page)
break;
}
builder.buildBackground()
builder.buildGameElements()
builder.buildForeground()
return builder.getPage()
}
}
export default PageBuildDirector
在面向对象编程中,对象不一定要被实例化,有时候使用静态方法就可以达到目的。在这个指挥者类中,只有一个静态方法,在这个静态方法中有两个case,一个负责建造主页,一个负责建造游戏结束页。无论是构建哪个页面,它们的建造顺序和建造方法是一致的。
接下来就是修改game.js代码,开始使用已经完成的建造者模式:
// game.js
...
import PageBuildDirector from './page/page_build_director'
...
class Game extends Event {
...
constructor() {
...
this.gameOverPage = PageBuildDirector.buildPage("gameOver") // 游戏结束页面
this.indexPage = PageBuildDirector.buildPage("index") // 主页
}
...
}
游戏的运行效果与之前一致:
最后总结一下,本小节应用了建造者模式,我们使用了两个页面构建类IndexPageBuilder和GameOverPageBuilder,分别完成游戏主页和游戏结束页面的构建。每个页面含有的页面元素不同,具体的构建过程也不尽相同,但拥有相同的”先背景、后中景、再前景“这样一个创建顺序,我们将这个创建顺序通过建造者模式固定下来。
因为我们的游戏很简单,页面也很简单,难以彰显建造者模式的强大;在一个拥有很有复杂对象的软件系统中,建造者模式可以让对象的创建变得简单清晰的作用才会完全显露出来。
本小节阶段源码见:https://github.com/rixingyike/wegame01/tree/5.2.1
我讲明白没有,欢迎留言讨论。
2021年02月6日