我是靠谱客的博主 愉快路人,最近开发中收集的这篇文章主要介绍游戏后端开发中的设计模式(1)创建型模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章会分成三篇,分别从创建型模式,结构型模式和行为模式三种划分,简单回顾下游戏开发的工作过程中对设计模式的使用。有纰漏之处,欢迎交流指正。

例子代码使用Golang实现,设计模式部分参考了refactoringguru.cn

创建型模式:是关于程序中对象创建的设计模式。游戏中服务器会创建许多具体的对象,例如玩家注册会创建玩家对象,玩家获得道具会创建道具对象,玩家在野外打怪会创建怪物对象等。下面会简单介绍几种常用的创建型模式。

文章目录

      • 1. 工厂方法模式
      • 2. 抽象工厂模式
      • 3. 生成器模式
      • 4. 单例模式

1. 工厂方法模式

简介

客户端通过工厂对象创建产品对象并进行调用,而不必关心其具体实现。工厂对象指定了产品对象应有的属性和方法,再委托给子对象进行具体的实现。客户端获取对象后,根据工厂提供的方法就能使用对象。

简单来说,客户指定了产品的用途之后,不需要关心工厂是怎么制造的,客户在获得产品后只要根据用途使用就可以了。

实现

  1. 让所有产品遵循同一接口(Product interface)。
  2. 创建工厂方法类(Creator),工厂方法类返回的对象符合产品接口。
  3. 为每种产品编写一个工厂方法子类(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. 生成器模式

简介

为了避免生成复杂对象时的巨型函数,需要把生成对象的步骤进行拆解划分,指定生成器接口,通过生成器对象实现的方法,按步骤生成对象。必要时候可以引入主管对象,根据生成器接口安排生成对象的步骤,传入不同的生成器能获取不同的对象。

实现

  1. 定义生成器接口(Builders interface),声明通用的产品构造步骤。
  2. 为每个产品(Product)实现具体生成器类(Concrete builders),实现构造步骤。
  3. 客户端创建生成器对象,通过该对象生产产品。

游戏里的应用

游戏里的玩家对象往往很复杂,创建玩家的时候会进行很多初始化的工作(例如初始化玩家属性,背包,任务等)。玩家的登陆形式有游客登陆和注册账号登陆两种,有时候我们需要根据登陆方式的不同,执行不同的玩家对象生成过程(例如游客不能做任务,有等级上限等)。

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 &registerBuilder{}
}
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. 单例模式

简介

保证一个类拥有全局唯一的对象。

实现

  1. 在类中添加一个私有成员变量保存单例实例。
  2. 声明获取单例的方法,在方法内实现“延迟初始化”。

游戏开发里的应用

一些全局的服务对象一般都是唯一的,例如排行榜对象,工会管理对象,活动管理对象等。当玩家去请求排行榜数据的时候,如果排行榜对象不存在,程序就会创建一个空的排行榜对象返回给玩家,如果已经存在,程序就会获取对象直接返回。

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)创建型模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(32)

评论列表共有 0 条评论

立即
投稿
返回
顶部