框架Framework
开头重点必须说一下-----------观察者 Observer
这个类的主要功能:就是注册一个类的实例和类的方法,啥时候需要调用这个方法的时候,由观察者组织传参并且调用,在当前这个框架模式中,观察者是用的比较重要的一种设计模式,理解观察者模式,对于理解这个框架还是比较重要的
通常情况,我们调用一个类的某一个非静态方法,都是将这个类实例化,然后用这个实例来显式的调用这个方法,而观察者模式是预先将这个类的实例和一些特定非静态方法绑定,等外界需要调用的时候,会通知这个观察者来调用
local Observer = class('Observer')
function Observer:ctor(notifyMethod, notifyContext)
self:setNotifyMethod(notifyMethod)
self:setNotifyContext(notifyContext)
end
function Observer:setNotifyMethod(notifyMethod)
self.notify = notifyMethod
end
function Observer:setNotifyContext(notifyContext)
self.context = notifyContext
end
function Observer:getNotifyMethod()
return self.notify
end
function Observer:getNotifyContext()
return self.context
end
function Observer:notifyObserver(notification)
self.notify(self.context, notification)
end
function Observer:compareNotifyContext(object)
return object == self.context
end
return Observer
统一管理之Facade
内部持有Command,View,Model,三个管理员,这个类是个单例,主要是为了统一管理而存在
Facade:registerCommand:注册命令类的实例
Facade:registerProxy:注册数据类的实例
Facade:registerMediator:注册面板类的实例
Facade:sendNotification(notificationName, body, type):调用命令来启动命令实例或者面板实例的某个方法
Facade:retrieveProxy(proxyName):获取某个数据类,ps想要获取这个数据类的一些数据
local Controller = require( framework.PACKAGE_NAME .. '/core/Controller')
local Model = require( framework.PACKAGE_NAME .. '/core/Model')
local View = require( framework.PACKAGE_NAME .. '/core/View')
local Notification = require( framework.PACKAGE_NAME .. '/patterns/observer/Notification')
local Facade = class("Facade")
function Facade:ctor(key)
if Facade.instanceMap[key] ~= nil then
error(Facade.MULTITON_MSG)
end
self:initializeNotifier(key)
Facade.instanceMap[key] = self
self:initializeFacade()
end
function Facade:initializeFacade()
self:initializeModel();
self:initializeController();
self:initializeView();
end
function Facade.getInstance(key)
if nil == key then
return nil
end
if Facade.instanceMap[key] == nil then
Facade.instanceMap[key] = Facade.new(key)
end
return Facade.instanceMap[key]
end
function Facade:initializeController()
if self.controller ~= nil then
return
end
self.controller = Controller.getInstance(self.multitonKey);
end
function Facade:initializeModel()
if self.model ~= nil then
return
end
self.model = Model.getInstance(self.multitonKey)
end
function Facade:initializeView()
if self.view ~= nil then
return
end
self.view = View.getInstance(self.multitonKey)
end
function Facade:registerCommand(notificationName, commandClassRef)
self.controller:registerCommand(notificationName, commandClassRef)
end
function Facade:removeCommand(notificationName)
self.controller:removeCommand(notificationName)
end
function Facade:hasCommand(notificationName)
return self.controller:hasCommand(notificationName)
end
function Facade:registerProxy(proxy)
self.model:registerProxy(proxy)
end
function Facade:retrieveProxy(proxyName)
return self.model:retrieveProxy(proxyName)
end
function Facade:removeProxy(proxyName)
local proxy = nil
if self.model ~= nil then
proxy = self.model:removeProxy(proxyName)
end
return proxy
end
function Facade:hasProxy(proxyName)
return self.model:hasProxy(proxyName)
end
function Facade:registerMediator(mediator)
if self.view ~= nil then
self.view:registerMediator(mediator)
end
end
function Facade:retrieveMediator(mediatorName)
return self.view:retrieveMediator(mediatorName)
end
function Facade:removeMediator(mediatorName)
local mediator = nil
if self.view ~= nil then
mediator = self.view:removeMediator(mediatorName)
end
return mediator
end
function Facade:hasMediator(mediatorName)
return self.view:hasMediator(mediatorName)
end
function Facade:sendNotification(notificationName, body, type)
g_lastMVCNotice = notificationName
self:notifyObservers(Notification.new(notificationName, body, type))
end
function Facade:notifyObservers(notification)
if self.view ~= nil then
self.view:notifyObservers(notification)
end
end
function Facade:initializeNotifier(key)
self.multitonKey = key
end
function Facade.hasCore(key)
return Facade.instanceMap[key] ~= nil
end
function Facade.removeCore(key)
if Facade.instanceMap[key] == nil then
return
end
Model.removeModel(key)
View.removeView(key)
Controller.removeController(key)
Facade.instanceMap[key] = nil
end
Facade.instanceMap = {}
Facade.MULTITON_MSG = "Facade instance for this Multiton key already constructed!";
return Facade
Facade管理下的-------面板 View
该类主要维持两个数据,一个是面板实例数组,一个面板实例观察者数组
对于一个面板而言:在实例数组里村一份,然后它可能有若干个感兴趣的事件,但只会生产一个观察者,然后将每一个感兴趣的事件与这个观察者绑定
消息机制,外界去操作面板的唯一入口就是面板的handleNotification的这个方法,所以我们用面板实例和这个方法去创建一个观察者就可以了
该类注册完观察者以后,当外界有消息来时,会去通知观察者数组,来判断当前是否有这个消息的观察者,如果有就调用
function View:ctor(key)
if View.instanceMap[key] ~= nil then
error(View.MULTITON_MSG)
end
self.multitonKey = key
View.instanceMap[self.multitonKey] = self
self.mediatorMap = {}
self.observerMap = {}
self:initializeView()
end
function View:initializeView() end
function View.getInstance(key)
if nil == key then
return nil
end
if View.instanceMap[key] == nil then
return View.new(key)
else
return View.instanceMap[key]
end
end
function View:registerObserver(notificationName, observer)
if self.observerMap[notificationName] ~= nil then
table.insert(self.observerMap[notificationName], observer)
else
self.observerMap[notificationName] = {observer}
end
end
function View:notifyObservers(notification)
if self.observerMap[notification:getName()] ~= nil then
local observers_ref = self.observerMap[notification:getName()]
for _, o in pairs(observers_ref) do
o:notifyObserver(notification)
end
end
end
function View:removeObserver(notificationName, notifyContext)
local observers = self.observerMap[notificationName]
for _, o in pairs(observers) do
if o:compareNotifyContext(notifyContext) then
table.remove(observers, _)
break
end
end
if #observers == 0 then
self.observerMap[notificationName] = nil
end
end
function View:registerMediator(mediator)
if self.mediatorMap[mediator:getMediatorName()] ~= nil then
return
end
mediator:initializeNotifier(self.multitonKey)
self.mediatorMap[mediator:getMediatorName()] = mediator
local interests = mediator:listNotificationInterests()
if #interests > 0 then
local observer = Observer.new(mediator.handleNotification, mediator)
for _, i in pairs(interests) do
self:registerObserver(i, observer)
end
end
mediator:onRegister()
end
function View:retrieveMediator(mediatorName)
return self.mediatorMap[mediatorName]
end
function View:removeMediator(mediatorName)
local mediator = self.mediatorMap[mediatorName]
if mediator ~= nil then
local interests = mediator:listNotificationInterests()
for _, i in pairs(interests) do
self:removeObserver(i, mediator)
end
self.mediatorMap[mediatorName] = nil
mediator:onRemove()
end
return mediator
end
function View:hasMediator(mediatorName)
return self.mediatorMap[mediatorName] ~= nil
end
function View.removeView(key)
View.instanceMap[key] = nil
end
View.instanceMap = {}
View.MULTITON_MSG = "View instance for this Multiton key already constructed!"
Facade管理下的-------命令command
这个命令command和上面的view面板是大致相同的,也是维护一个命令实例数组,和生产一个观察者数组,只是这个观察者绑定的函数时当前命令的executeCommand这个函数,当外界发送消息通知观察者的时候,会调用当前命令的executeCommand这个函数,然后从当前命令实例数组中去找命令实例,如果存在则调用,这里需要注意,commond中用的view实例是单例,整个游戏正常情况也只有一个view单例,所以我们在facade中即使是注册命令,其最后生产的观察者也是存在同一个view实例中
local Controller = class('Controller')
function Controller:ctor(key)
if Controller.instanceMap[key] ~= nil then
error(Controller.MULTITON_MSG)
end
self.multitonKey = key
Controller.instanceMap[self.multitonKey] = self
self.commandMap = {}
self:initializeController()
end
function Controller:initializeController()
self.view = View.getInstance(self.multitonKey);
end
function Controller.getInstance(key)
if nil == key then
return nil
end
if(nil == Controller.instanceMap[key]) then
return Controller.new(key)
else
return Controller.instanceMap[key]
end
end
function Controller:executeCommand(note)
local commandClassRef = self.commandMap[note:getName()]
if(commandClassRef == nil) then
return
end
local commandInstance = commandClassRef.new()
commandInstance:initializeNotifier(self.multitonKey)
commandInstance:execute(note)
end
function Controller:registerCommand(notificationName, commandClassRef)
if(self.commandMap[notificationName] == nil) then
self.view:registerObserver(notificationName, Observer.new(self.executeCommand, self));
end
self.commandMap[notificationName] = commandClassRef
end
function Controller:hasCommand(notificationName)
return self.commandMap[notificationName] ~= nil
end
function Controller:removeCommand(notificationName)
if self:hasCommand(notificationName) then
self.view:removeObserver(notificationName, self)
self.commandMap[notificationName] = nil
end
end
function Controller.removeController(key)
Controller.instanceMap[key] = nil
end
Controller.instanceMap= {}
Controller.MULTITON_MSG= "controller key for this Multiton key already constructed"
return Controller
Facade管理下的-------数据 Model
Model主要管理model实例,我们在游戏加载的时候,就实例化各个模块的model,最后调用的时候只需要retrieveProxy就可以使用了啊
local Model = class('Model')
function Model:ctor(key)
if Model.instanceMap[key] then
error(Model.MULTITON_MSG)
end
self.multitonKey = key
Model.instanceMap[key] = self
self.proxyMap = {}
self:initializeModel()
end
function Model:initializeModel() end
function Model.getInstance(key)
if nil == key then
return nil
end
if Model.instanceMap[key] == nil then
return Model.new(key)
else
return Model.instanceMap[key]
end
end
function Model:registerProxy(proxy)
proxy:initializeNotifier(self.multitonKey)
self.proxyMap[proxy:getProxyName()] = proxy
proxy:onRegister()
end
function Model:retrieveProxy(proxyName)
return self.proxyMap[proxyName]
end
function Model:hasProxy(proxyName)
return self.proxyMap[proxyName] ~= nil
end
function Model:removeProxy(proxyName)
local proxy = self.proxyMap[proxyName]
if proxy ~= nil then
self.proxyMap[proxyName] = nil
proxy:onRemove()
end
return proxy
end
function Model.removeModel(key)
Model.instanceMap[key] = nil
end
Model.instanceMap = {}
Model.MULTITON_MSG= "Model instance for this Multiton key already constructed!";
return Model
供外界继承的三剑客(model,view,control)
最顶层父类
local Facade = require( framework.PACKAGE_NAME .. '/patterns/facade/Facade')
local Notifier = class("Notifier")
function Notifier:ctor()
end
function Notifier:sendNotification(notificationName, body, type)
local facade = self:getFacade()
if facade ~= nil then
facade:sendNotification(notificationName, body, type)
end
end
function Notifier:initializeNotifier(key)
self.multitonKey = key
self.facade = self:getFacade()
end
function Notifier:getFacade()
if self.multitonKey == nil then
error(Notifier.MULTITON_MSG)
end
return Facade.getInstance(self.multitonKey)
end
Notifier.MULTITON_MSG = "multitonKey for this Notifier not yet initialized!";
return Notifier
视图
外界的视图类都应该继承此类
重写listNotificationInterests这个方法,将这个面板感兴趣的事件,列出来,比如面板的打开,面板的关闭,面板的刷新等
重写handleNotification这个方法,当有消息来的时候,会首先访问view的观察者数组,找到观察者,如果存在,那这个方法就会被框架调用
local Notifier = require( framework.PACKAGE_NAME .. '/patterns/observer/Notifier')
local Mediator = class('Mediator', Notifier)
function Mediator:ctor(mediatorName, viewComponent)
self._mediatorName = mediatorName or Mediator.NAME
self._viewComponent = viewComponent
end
Mediator.NAME = 'Mediator'
function Mediator:getMediatorName()
return self._mediatorName
end
function Mediator:setViewComponent(viewComponent)
self._viewComponent = viewComponent
end
function Mediator:getViewComponent()
return self._viewComponent
end
function Mediator:listNotificationInterests()
return {}
end
function Mediator:handleNotification(notification)
end
function Mediator:onRegister()
end
function Mediator:onRemove()
end
return Mediator
命令
对于既不是面板也不是数据的一些类,可以考虑继承此类,
execute:这个函数会被框架自动调用
ocal Notifier = require( framework.PACKAGE_NAME .. '/patterns/observer/Notifier')
local SimpleCommand = class('SimpleCommand', Notifier)
function SimpleCommand:ctor()
SimpleCommand.super.ctor(self)
end
function SimpleCommand:execute(notification)
end
return SimpleCommand
数据
重新onRegister()这个函数,将服务器消息注册一下,这个函数会被框架自动调用
local Notifier = require( framework.PACKAGE_NAME .. '/patterns/observer/Notifier')
local Proxy = class('Proxy', Notifier)
function Proxy:ctor(proxyName, data)
self.proxyName = proxyName or Proxy.NAME
if data ~= nil then
self:setData(data)
end
end
Proxy.NAME = "Proxy"
function Proxy:getProxyName()
return self.proxyName
end
function Proxy:setData(data)
self.data = data
end
function Proxy:getData()
return self.data
end
function Proxy:onRegister()
end
function Proxy:onRemove()
end
return Proxy
基础使用
注册面板和发送面板消息
注册一个面板实例,同时将生成一个观察者,这个观察者绑定面板实例的一个方法handleNotification,这个方法非常重要,他是观察者操作这个面版实例的一个重要方法,每一个面板会存在若干个感兴趣的事件,而每一个事件名都会和这个观察者绑定,当外部触发某一个事件的时候,就会从队列中寻找是否存在这个事件,如果存在就会调用观察者来响应这次触发,即调用handleNotification这个函数,并且传参
--外界创建一个media的面板
local mediatorInst = Mediator.new()
facade:registerMediator(mediatorInst)
function Facade:registerMediator(mediator)
if self.view ~= nil then
self.view:registerMediator(mediator)
end
end
function Facade:sendNotification(notificationName, body, type)
g_lastMVCNotice = notificationName
self:notifyObservers(Notification.new(notificationName, body, type))
end
function Facade:notifyObservers(notification)
if self.view ~= nil then
self.view:notifyObservers(notification)
end
end
注册命令和发送命令消息
local StartUpCommand = requireCommand( "gamestates/StartUpCommand" )
global.Facade = facade
facade:registerCommand( noticeTable.StartUp, StartUpCommand )
facade:sendNotification( noticeTable.StartUp )
注册数据和使用数据
local Proxy = requireProxy("PlayerProperty.lua")
local proxyInst = Proxy.new()
facade:registerProxy(proxyInst)
local playerPropertyProxy = global.Facade:retrieveProxy(global.ProxyTable.PlayerProperty)
local roleLevel = playerPropertyProxy:GetRoleLevel()
巧妙使用
在做游戏的时候,存在三个行为:
第一个行为:我们往往有数百个数据类,每一个数据类都有要和服务器交互的数据,当然在游戏中只需要require一个即可,可以考虑将这些数据类的引用路径单独放在一个表中,当我们启动游戏的时候,直接获取这个表,就可以完成所有数据类的注册;
第二个行为:数据类如此,面板类也是这样,游戏中也可能存在大量的面板,每一个面板都有一个media来管理,那么也可以生成一个media table来管理名字和引用路径,当启动游戏的时候,直接访问这个表,就可以注册所有的media;
第三个行为:控制类也是如此,也可以考虑将控制的名字和引用路径放到一张表中,在游戏启动的时候,将注册所有的controller
上面的三个行为,都和数据和视图无关,可以考虑将三个行为做成三个命令,每个命令就主要负责注册任务
形如:
--注册controller
function RegisterWorldControllerCommand:execute(note)
local requireCommand = requireCommand
local registerTable = requireCommand("register/WorldControllerTable")
local facade = global.Facade
for k, v in pairs(registerTable) do
local cmd = requireCommand(v)
facade:registerCommand(k, cmd)
end
end
--注册model
function RegisterWorldProxyCommand:registerProxy()
local requireProxy = requireProxy
local registerTable = requireCommand("register/WorldProxyTable")
local facade = global.Facade
local size = #registerTable
local loadingUnit = 89/size
local function callback(param)
local Proxy = requireProxy(param)
local proxyInst = Proxy.new()
facade:registerProxy(proxyInst)
end
-- add loading task
global.LoadingHelper:AddLoadingTask( registerTable, callback, loadingUnit, size )
end
--注册mediator
function RegisterWorldMediatorCommand:RegisterNormalLayerMediator()
local requireMediator = requireMediator
local registerTable = requireCommand("register/WorldMediatorTable")
local facade = global.Facade
local function callback(param)
local Mediator = requireMediator(param)
local mediatorInst = Mediator.new()
facade:registerMediator(mediatorInst)
end
local nSize = #registerTable
local loadingUnit = 10 / (nSize)
-- add loading task
global.LoadingHelper:AddLoadingTask(registerTable, callback, loadingUnit, nSize)
end
--触发加载
function LoadingBeginCommand:registerLogic()
-- register
local RegisterWorldController = requireCommand( "register/RegisterWorldControllerCommand" )
local RegisterWorldMediator = requireCommand( "register/RegisterWorldMediatorCommand" )
local RegisterWorldProxy = requireCommand( "register/RegisterWorldProxyCommand" )
local cmd = nil
cmd = RegisterWorldController.new()
global.Facade:registerCommand( global.NoticeTable.RegisterWorldController, cmd )
cmd = RegisterWorldMediator.new()
global.Facade:registerCommand( global.NoticeTable.RegisterWorldMediator, cmd )
cmd = RegisterWorldProxy.new()
global.Facade:registerCommand( global.NoticeTable.RegisterWorldProxy, cmd )
global.Facade:sendNotification( global.NoticeTable.RegisterWorldController )
global.Facade:sendNotification( global.NoticeTable.RegisterWorldProxy )
global.Facade:sendNotification( global.NoticeTable.RegisterWorldMediator )
end
command和mediator争议
框架中对于这两个的处理其实差不多,他们的观察者都注册在同一个实例view中,他们也都是基于消息的,只是command只能处理一条消息,你可以理解为,当你发送command消息的时候,就会去通知当时注册的时候生成的观察者,然后观察者去执行取出绑定的函数和实例,这个函数其实就是controller的executeCommand函数,然后再从controller的命令实例数组中去找实例,最后执行execute,可以明显的感觉到,command只是执行一个消息,那mediator就不一样了,他对同样一个mediator实例可以注册若干个消息,所以mediator的处理能力要更大一些,但不管mediator有多少个感兴趣的事件,其最终还是一个mediator实例也只可以生成一个观察者,观察者都最终放在一个view实例数组中,通过key,value存储,key存储的是消息的名字,value存储的是观察者;
分析到此,对于command和mediator的使用就可以自己选择了,一对一关系的可以选择使用command,比如注册等,一对多关系的可以使用mediator,比如UI面板,ui面板的打开,关闭,刷新就是有多条消息
mediator 应用场景之状态机
游戏的状态如下:
GAME_STATE_ACTIVE = "active", -- 启动
GAME_STATE_PATCH = "patch", -- 补丁更新
GAME_STATE_INIT = "init", -- 初始化
GAME_STATE_LOGIN = "login", -- 登陆
GAME_STATE_ROLE = "role", -- 选角
GAME_STATE_WORLD = "world", -- 游戏世界
可以考虑生成一个继承mediator的GameStateController.lua
如下:负责管理的状态切换,状态在切换的时候就干了两件事,进入和关闭,而我们在外界只要发消息,该控制就可以收到消息,然后处理状态的进入和关闭
local GameStateController = class('GameStateController', framework.Mediator)
GameStateController.NAME = "GameStateController"
local gameStateActive = require( "gamestates/gameStateActive" )
local gameStateInit = require( "gamestates/gameStateInit" )
local gameStateLogin = require( "gamestates/gameStateLogin" )
local gameStateRole = require( "gamestates/gameStateRole" )
local gameStateWorld = require( "gamestates/gameStateWorld" )
function GameStateController:ctor()
GameStateController.super.ctor( self, self.NAME )
self:Init()
end
function GameStateController:listNotificationInterests()
local noticeTable = global.NoticeTable
return {
noticeTable.ChangeGameState,
noticeTable.ReleaseMemory,
}
end
function GameStateController:handleNotification(notification)
local noticeID = notification:getName()
local notices = global.NoticeTable
local data = notification:getBody()
if notices.ChangeGameState == noticeID then
self:ChangeGameState( data )
elseif notices.ReleaseMemory == noticeID then
self:Cleanup()
end
end
function GameStateController:Init()
local mmo = global.MMO
self._gameStates = {}
self._currStateID = nil
self._currState = nil
self._gameStates[mmo.GAME_STATE_ACTIVE] = gameStateActive.new()
self._gameStates[mmo.GAME_STATE_INIT] = gameStateInit.new()
self._gameStates[mmo.GAME_STATE_LOGIN] = gameStateLogin.new()
self._gameStates[mmo.GAME_STATE_ROLE] = gameStateRole.new()
self._gameStates[mmo.GAME_STATE_WORLD] = gameStateWorld.new()
end
function GameStateController:Cleanup()
if self._currState then
print( self._currStateID, "Cleanup" )
self._currState:Cleanup()
end
end
function GameStateController:ChangeGameState( newStateID )
if newStateID == self._currStateID then
return
end
local newState = self._gameStates[newStateID]
if nil == newState then
return
end
local lastState = self._currState
if self._currState then
self._currState:onExit()
end
self._currState = newState
self._currStateID = newStateID
if lastState then
print( lastState:getStateType(), "-->", self._currState:getStateType() )
else
print( "none -->", self._currState:getStateType() )
end
newState:onEnter()
end
function GameStateController:ChangeAppStateCB( appState )
if 0 == appState then
self:OnGameSuspend()
elseif 1 == appState then
self:OnGameResumed()
end
end
function GameStateController:OnGameSuspend()
self:getFacade():sendNotification( global.NoticeTable.GameSuspend )
end
function GameStateController:OnGameResumed()
self:getFacade():sendNotification( global.NoticeTable.GameResumed )
end
function GameStateController:GetCurrentState()
return self._currStateID
end
function GameStateController:onRegister()
GameStateController.super.onRegister( self )
-- stateMgr
if not global.gameStateCtl then
global.gameStateCtl = gameStateMgr:Inst()
end
global.gameStateCtl:RegisterLuaHandler( handler( self, self.ChangeAppStateCB ) )
end
function GET_GAME_STATE()
local stateCtl = global.Facade:retrieveMediator( GameStateController.NAME )
return stateCtl:GetCurrentState()
end
return GameStateController