[理解实践PureMVC框架]5-最后一篇,总结和例子演示

MVC框架其实更多是在web前端使用的,一个web页面就是一个view嘛,然后可以很好地将数据、界面、控制分成MVC三层。但是游戏开发中会有点不一样,就我的感觉来说,我觉得PureMVC更适合休闲益智类的游戏,因为这类游戏就几个页面,页面中的元素也比较简单,我在网上搜索发现,有人开发棋牌游戏就用过这个框架的。而RPG类的游戏可能就不太适用这个框架了,像游戏场景中的角色的行为,就无法很好的划分为MVC三层。当然,上面所说的都是我学习了解他人总结的基础上得出的自己的总结,最佳体会还是来自于实践。每个框架都不是万能的,学习这个框架,可能有用,也可能没用,并不是学了之后做什么都要生搬硬套上去。下面举的例子,是我用ooc-lang写的,模拟了一个很简单的场景,仅供演示用。

由于ooc-lang并不是某游戏引擎绑定的用来开发游戏的语言,所以无法写几行简单的代码就可以展示出来一个界面,虽然它可以用SDL这个库来写界面,但是太过底层,需写的代码较多,因此我这个例子就没有界面演示了,只是在控制台输出打印下日志。

看代码前,先讲下整个代码的结构和这个程序的执行过程,这样看代码的时候才知道怎么看。用PureMVC框架写代码的时候,只需自己继承并实现一个自己的Facade类就行了,该Facade会初始化好MVC三层,一般这三层也不需要我们自己再实现了,然后我们就可以实现各种需要的中介、命令、代理类了,在然后就在需要的地方注册这些类或者发送相关通知。界面元素的引用一般放在单独的一个类里,常量什么的放在一个配置文件里。最后,在程序启动的时候,调用一下该Facade的初始化命令就行了。给个截图看下我这个例子的代码结构:
[理解实践PureMVC框架]5-最后一篇,总结和例子演示_第1张图片
你可以简单想象一下,就是程序启动后会有一个界面,这个界面就是菜单界面,界面显示有草鞋、草帽的数量和价格。程序启动后会立马发送一个更新菜单的通知,更新菜单后会立马发送一个打开菜单的通知,之后就会显示价格和数量了。整个流程就是这样,但实际上是没有界面的。好了,接下来就看代码了,不多。

先从程序入口开始:

/*
 *	Main.ooc
 */
 
// 这句是为了描述PureMVC框架源码的详情的,没有它编译的时候会找不到对应文件的源码
use PureMVC
// 引入模块,这个是我们自己实现的外观类
import AppFacade

Main: class {
	init: func

	start: func {
		// 程序启动后,调用外观类的启动方法
		// 这里就包括了外观类的初始化和MVC三层的初始化,以及startUp()中的启动逻辑。具体看外观类
		AppFacade getInstance() startUp()
	}
}

// 程序入口,就像C中的main()一样。
main: func {
	Main new() start()
}

全局唯一的一个外观类:

/*
 *	AppFacade.ooc
 */

// 引入相关模块
import patterns/facade/Facade				// PureMVC框架中的Facade类,我们需要继承它
import controller/StartupCommand			// 自己实现的启动命令类
import controller/ShowPriceCommand	// 自己实现的显示价格的命令类

AppFacade: class extends Facade {
	// 字符常量,用于映射对应的命令和发送通知时用
	// 如果通知多的话,一般是独立放在一个模块里,而不会全集中到这个外观类里
	startup := static "startup"		// 启动命令
	updateMenuView := static "update_menu_view"	// 更新菜单命令
	openMenu := static "open_menu"	// 打开菜单命令
	showPrice := static "show_price"	// 显示价格命令

	// 类的构造函数,ooc-lang中,子类需要调用父类的构造函数的话,只需前面加个super就行了
	init: super func

	// 重新定义一个和父类一样的获取单例的方法,注意静态方法不能被重写,所以这里是重新定义
	getInstance: static func -> AppFacade {
		if (This instance == null) {
			This instance = AppFacade new()
		}
		return This instance as AppFacade
	}

	// 重写父类中初始化Controller层的方法,因为我们需要额外做其它事
	initializeController: func {
		// 除了在func前加super,函数体内也可以通过调用super()来调用父类中的同一个方法
		super()

		// 注册启动命令
		registerCommand(This startup, StartupCommand new())
		// 注册显示价格的命令
		registerCommand(This showPrice, ShowPriceCommand new())
		/* 那些命令注册好后就会有一个观察者监听着,一旦收到通知,就立马执行这些命令 */
	}

	// 程序入口中调用到的启动方法
	startUp: func {
		// 启动后发送一个启动命令
		sendNotification(This startup)
	}
}

一个简单的外观类就实现了,其中我们重写了initializeController()这个方法,另外两个initializeModel()InitializeView()是直接用默认的就行了,因为我们不需要实现额外的逻辑。然后还额外实现了一个startUp()方法,一般都需要一个方法来初始化整个框架的。

接下来看controller文件夹下的模块了,前面的文章说过,Controller层并不需要自己实现一个Controller类,我们只需实现一个个的命令就行了:

/*
 *	StartupCommand.ooc
 *	启动命令
 */
 
// 引入相关模块
import patterns/command/SimpleCommand
import patterns/observer/Notification

import view/components/MenuView	// 菜单类,存放界面元素的引用
import view/MenuViewMediator	// 菜单界面的中介,用来操作菜单界面
import model/vo/Inventory	// 库存类,存放数据
import model/InventoryProxy	// 库存代理,通过代理来访问库存数据

// 启动命令,继承自SimpleCommand,因为这里只需执行一条命令就行了
StartupCommand: class extends SimpleCommand {
	init: func

	// 重写方法,写上具体需要执行的逻辑
	execute: func(notification: Notification) {
		// 打印跟踪
		"[ StartupCommand ] execute()" println()

		// 启动之后,注册菜单界面中介和库存代理
		// 一般字符常量放在一个独立的模块中,这里我就懒得写多一个文件,就直接写这里了
		facade registerMediator(MenuViewMediator new("MenuViewMediator", MenuView new()))

		facade registerProxy(InventoryProxy new("InventoryProxy", Inventory new()))
	}
}
/*
 *	ShowPrice.ooc
 *	显示价格的命令
 */

// 引入相关模块
import patterns/command/MacroCommand

import SellShoesCommand, SellHatCommand

// 为了演示MacroCommand命令,我这里将显示价格命令分开来了,分别为卖草鞋的命令和卖草帽的命令
ShowPriceCommand: class extends MacroCommand {
	// 子类构造函数需要调用一下父类的构造函数
	init: super func

	// 重写该方法,其实就是往里头添加子命令就行了
	initializeMacroCommand: func {
		addSubCommand(SellShoesCommand new())
		addSubCommand(SellHatCommand new())
	}
}
/*
 *	SellShoesCommand.ooc
 *	卖草鞋的命令
 */
 
import patterns/command/SimpleCommand
import patterns/observer/Notification

// 继承SimpleCommand就行了
SellShoesCommand: class extends SimpleCommand {
	init: func

	// 重写方法
	execute: func(notification: Notification) {
		// 简单输出打印一下价格就行了,如果有界面的话,可以在这里更新界面上的价格
		// 一般数据是从代理中获取的,但是为了偷懒,就没写价格代理了,直接引用数据就行了
		"[ SellShoesCommand ] 草鞋3元一双,10元三双" println()
	}
}
/*
 *	SellHatCommand.ooc
 *	卖草帽的命令
 */
 
import patterns/command/SimpleCommand
import patterns/observer/Notification

// 同卖草鞋的命令一样
SellHatCommand: class extends SimpleCommand {
	init: func

	execute: func(notification: Notification) {
		"[ SellHatCommand ] 草帽5元一顶" println()
	}
}

controller文件夹下的模块讲完了就讲model文件夹下的模块了,Model层也不用我们实现一个Model类,只需实现真实的数据类和数据的代理类就行了:

先看vo文件夹下的,这个文件夹一般是放实际的数据类,我也不清楚vo的全称是什么,但我看官方例子都是将实际的数据类放这里。

/*
 * Inventory.ooc
 * 库存数据类
 */
 
// 数据可能是从远程获取到的,也可能是启动后就初始化了的,这里是直接初始化了。
Inventory: class {
	numShoes := 10	// 草鞋数量
	numHats  := 8	// 草帽数量

	init: func
}
/*
 *	InventoryProxy.ooc
 *	库存数据的代理,通过代理访问数据,而不是直接访问
 */
 
import patterns/proxy/Proxy

import AppFacade
import vo/Inventory

// 我看官方例子中,有直接将数据写代理类里的,也有将数据独立成一个类的
// 可能简单的数据就直接放代理类里头了吧,复杂的就独立成一个类
InventoryProxy: class extends Proxy {
	// 库存数据的引用,避免每次都通过函数来获取
	inventory: Inventory

	// 构造是需要设置该代理的名称和数据
	init: func(proxyName: String, data: Pointer = null) {
		super(proxyName, data)
	}

	// 重写该方法,注册代理时调用
	onRegister: func {
		// 获取数据
		inventory = getData() as Inventory

		// 注册库存代理时会获取到库存信息,然后发送一条更新菜单界面的通知
		sendNotification(AppFacade updateMenuView, inventory)
	}
}

最后就是view文件夹下的模块了,components文件夹都是用来放界面元素的引用的,通过中介来管理和更新这些元素:

/*
 *	MenuView.ooc
 *	菜单界面(CocosCreator里不是有很多脚本吗,那些脚本中有声明场景中的各个对象,你可以将此模块想象成类似的脚本)
 */
 
import AppFacade

MenuView: class {
	// 假设是界面中需显示的文本,这里我们只是将它打印出来,并不是显示到界面上
	numShoes: Int = 0
	numHats:  Int = 0

	init: func

	// 界面的更新方法,更新界面的时候调用
	update: func(numShoes, numHats: Int) {
		this numShoes = numShoes
		this numHats  = numHats

		// 输出打印
		"[ MenuView ] 草鞋数量:#{this numShoes}" println()
		"[ MenuView ] 草帽数量:#{this numHats}" println()

		// 更新库存后发送一条打开菜单的通知
		AppFacade getInstance() sendNotification(AppFacade openMenu)
	}
}
/*
 *	MenuViewMediator.ooc
 *	菜单界面中介,管理和更新菜单界面
 */
 
import patterns/mediator/Mediator
import patterns/observer/Notification

import AppFacade
import components/MenuView
import model/vo/Inventory

MenuViewMediator: class extends Mediator {
	// 界面类的引用,避免每次都通过函数来获取
	menuView: MenuView

	// 构造一个中介时需要设置中介的名称和界面元素
	init: func(mediatorName: String, viewComponent: Pointer = null) {
		super(mediatorName, viewComponent)
	}

	// 列出该中介感兴趣的通知,注册中介时会注册观察者来监听这些通知
	listNotificationInterests: func -> String[] {
		[AppFacade updateMenuView, AppFacade openMenu] as String[]
	}

	// 收到通知时需要执行的逻辑,一般界面的更新就是在这里了
	handleNotification: func(notification: Notification) {
		// 获取通知名称
		match(notification getName()) {
			// 如果是更新菜单界面的通知
			case AppFacade updateMenuView =>
				// 获取发通知时携带的消息体,在这里是库存数据,可以看发通知的地方,会附带该参数的
				body := notification getBody() as Inventory

				// 如果菜单界面不为空
				if (menuView != null) {
					// 更新菜单界面的库存数据
					menuView update(body numShoes, body numHats)
				}
			// 如果是打开菜单界面的通知
			case AppFacade openMenu =>
				// 打开了菜单界面,当然是发送一条显示价格的通知来显示价格了
				sendNotification(AppFacade showPrice)
			// 缺省情况下的处理
			case =>
				super(notification)
		}
	}

	// 注册中介时调用
	onRegister: func {
		if (getViewComponent() != null) {
			// 注册中介时会设置viewComponent嘛,我们保存一个引用,避免每次都调用函数来获取
			menuView = getViewComponent() as MenuView
		}
	}
}

例子的所有代码都贴出来了,编译运行的结构看截图:
[理解实践PureMVC框架]5-最后一篇,总结和例子演示_第2张图片
看日志就知道程序是怎么执行了的吧,我把框架的初始化过程也打印出来了。用ooc-lang实现一遍PureMVC框架并写个简单例子的系列就完结了。收获不能说没有,但是大不大就不清楚了。

你可能感兴趣的:(PureMVC框架)