概述
文章会分成三篇,分别从创建型模式,结构型模式和行为模式三种划分,简单回顾下游戏开发的工作过程中对设计模式的使用。有纰漏之处,欢迎交流指正。
例子代码使用Golang实现,设计模式部分参考了refactoringguru.cn
创建型模式:是关于程序中对象创建的设计模式。游戏中服务器会创建许多具体的对象,例如玩家注册会创建玩家对象,玩家获得道具会创建道具对象,玩家在野外打怪会创建怪物对象等。下面会简单介绍几种常用的创建型模式。
文章目录
- 1. 工厂方法模式
- 2. 抽象工厂模式
- 3. 生成器模式
- 4. 单例模式
1. 工厂方法模式
简介
客户端通过工厂对象创建产品对象并进行调用,而不必关心其具体实现。工厂对象指定了产品对象应有的属性和方法,再委托给子对象进行具体的实现。客户端获取对象后,根据工厂提供的方法就能使用对象。
简单来说,客户指定了产品的用途之后,不需要关心工厂是怎么制造的,客户在获得产品后只要根据用途使用就可以了。
实现
- 让所有产品遵循同一接口(Product interface)。
- 创建工厂方法类(Creator),工厂方法类返回的对象符合产品接口。
- 为每种产品编写一个工厂方法子类(Concrete creator),子类实现具体的创建过程,并返回具体的产品(Concrete product)。
游戏开发中的使用
典型例如游戏内的道具(Item)会有很多种,比如装备(equip)和宝石(stone)等,他们的用途不同,但都会有添加增加(addNum)和删除(decNum)的操作。通过工厂方法,我们调用添加和删除物品的方法时就不需要关心具体的实现。
Go的接口本身既是工厂的“设计图”,又是客户端的“说明书”,用来实现工厂方法再合适不过了。
package main
import "fmt"
//作为“设计图”与“说明书”的接口
type item interface {
addNum(int)
decNum(int)
getNum() int
}
//装备类
type equip struct {
equipNum int
}
//宝石类
type stone struct {
stoneNum int
}
func (e *equip) addNum(num int) {
e.equipNum += num
}
func (e *equip) decNum(num int) {
e.equipNum -= num
}
func (e *equip) getNum() int {
return e.equipNum
}
func (s *stone) addNum(num int) {
s.stoneNum += num
}
func (s *stone) decNum(num int) {
s.stoneNum -= num
}
func (s *stone) getNum() int {
return s.stoneNum
}
//“装备子类”的实现
func newEquip() *equip {
return &equip{
equipNum: 1,
}
}
//“宝石子类”的实现
func newStone() *stone {
return &stone{
stoneNum: 20,
}
}
//“工厂类”
func getItem(itemType string) (item, error) {
if itemType == "equip" {
return newEquip(), nil
}
if itemType == "stone" {
return newStone(), nil
}
return nil, fmt.Errorf("Wrong item type passed")
}
func main() {
e, _ := getItem("equip")
e.addNum(1)
fmt.Printf("equip num: %dn", e.getNum())
s, _ := getItem("stone")
s.decNum(10)
fmt.Printf("stoen num: %dn", s.getNum())
}
2. 抽象工厂模式
简介
在工厂方法模式上再“套了一层”,把“工厂类”抽象出来,待客户端执行的时候再通过“工厂的工厂”生成“具体的工厂”,再通过“具体的工厂”生产对象。
典型的类似于Go语言在用户输入相同的情况下,在不同平台上根据操作系统指令的不同生成不同的可执行文件,最后我们运行可执行文件的结构都是一样的,相当于Go根据不同操作系统生成了不同的“机器码工厂”来生产代码。
3. 生成器模式
简介
为了避免生成复杂对象时的巨型函数,需要把生成对象的步骤进行拆解划分,指定生成器接口,通过生成器对象实现的方法,按步骤生成对象。必要时候可以引入主管对象,根据生成器接口安排生成对象的步骤,传入不同的生成器能获取不同的对象。
实现
- 定义生成器接口(Builders interface),声明通用的产品构造步骤。
- 为每个产品(Product)实现具体生成器类(Concrete builders),实现构造步骤。
- 客户端创建生成器对象,通过该对象生产产品。
游戏里的应用
游戏里的玩家对象往往很复杂,创建玩家的时候会进行很多初始化的工作(例如初始化玩家属性,背包,任务等)。玩家的登陆形式有游客登陆和注册账号登陆两种,有时候我们需要根据登陆方式的不同,执行不同的玩家对象生成过程(例如游客不能做任务,有等级上限等)。
package main
import "fmt"
type iBuilder interface {
setPlayerState()
setPackage()
setTask()
getPlayer() player
}
func getBuilder(builderType string) iBuilder {
if builderType == "guest" {
return newGuestBuilder()
}
if builderType == "register" {
return newRegisterBuilder()
}
return nil
}
type guestBuilder struct {
hp, atk, def int
packageNum int
task []int
}
func newGuestBuilder() *guestBuilder {
return &guestBuilder{}
}
func (b *guestBuilder) setPackage() {
b.packageNum = 10 //限制背包数
}
func (b *guestBuilder) setTask() {
b.task = nil //限制任务
}
func (b *guestBuilder) setPlayerState() {
b.atk, b.def, b.hp = 10000, 10000, 50000 //加大属性
}
func (b *guestBuilder) getPlayer() player {
return player{
atk: b.atk, def: b.def, hp: b.hp,
packageNum: b.packageNum,
task: b.task,
}
}
type registerBuilder struct {
atk, def, hp int
packageNum int
task []int
}
func newRegisterBuilder() *registerBuilder {
return ®isterBuilder{}
}
func (b *registerBuilder) setPackage() {
b.packageNum = 100 //加大背包格子
}
func (b *registerBuilder) setTask() {
b.task = []int{1, 2, 3} //分配任务
}
func (b *registerBuilder) setPlayerState() {
b.atk, b.def, b.hp = 100, 100, 500 //正常属性
}
func (b *registerBuilder) getPlayer() player {
return player{
hp: b.hp, def: b.def, atk: b.atk,
packageNum: b.atk,
task: b.task,
}
}
type player struct {
atk, def, hp int
packageNum int
task []int
}
type director struct {
builder iBuilder
}
func newDirector(b iBuilder) *director {
return &director{
builder: b,
}
}
func (d *director) setBuilder(b iBuilder) {
d.builder = b
}
//定义执行构建对象的步骤
func (d *director) createPlayer() player {
d.builder.setPlayerState()
d.builder.setTask()
d.builder.setPackage()
return d.builder.getPlayer()
}
func main() {
gBuilder := getBuilder("guest")
rBuilder := getBuilder("register")
d := newDirector(gBuilder)
gPlayer := d.createPlayer()
fmt.Printf("Guest Player State: %d,%d,%dn", gPlayer.atk, gPlayer.def, gPlayer.hp)
fmt.Printf("Guest Player PackageNum: %dn", gPlayer.packageNum)
fmt.Printf("Guest Player Task: %+vn", gPlayer.task)
d.setBuilder(rBuilder)
rPlayer := d.createPlayer()
fmt.Printf("nRegister Player State: %d,%d,%dn", rPlayer.atk, rPlayer.def, rPlayer.hp)
fmt.Printf("Register Player PackageNum: %dn", rPlayer.packageNum)
fmt.Printf("Register Player Task: %+vn", rPlayer.task)
}
4. 单例模式
简介
保证一个类拥有全局唯一的对象。
实现
- 在类中添加一个私有成员变量保存单例实例。
- 声明获取单例的方法,在方法内实现“延迟初始化”。
游戏开发里的应用
一些全局的服务对象一般都是唯一的,例如排行榜对象,工会管理对象,活动管理对象等。当玩家去请求排行榜数据的时候,如果排行榜对象不存在,程序就会创建一个空的排行榜对象返回给玩家,如果已经存在,程序就会获取对象直接返回。
package main
import (
"fmt"
"sync"
)
type ranking struct {
sync.Mutex
rankingList map[int]int
}
var globalRanking ranking
func getRankingList() map[int]int {
globalRanking.Lock()
defer globalRanking.Unlock()
if globalRanking.rankingList == nil {
fmt.Println("Creating single instance now.")
globalRanking.rankingList = make(map[int]int)
} else {
fmt.Println("Single instance already created.")
}
return globalRanking.rankingList
}
func main() {
for i := 0; i < 5; i++ {
go getRankingList()
}
_, _ = fmt.Scanln()
}
最后
以上就是愉快路人为你收集整理的游戏后端开发中的设计模式(1)创建型模式的全部内容,希望文章能够帮你解决游戏后端开发中的设计模式(1)创建型模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复