百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

框架设计并不是简单粗暴地写代码,而是要先弄清逻辑

ccwgpt 2025-08-06 19:01 2 浏览 0 评论

3. 框架设计

3. 框架设计

本节我们要开发一个UI框架,底层以白鹭引擎为例。

框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。

一个一个的UI窗口是独立的吗?不是的,因为它有层级关系,A窗口在B窗口下面,新创建的C窗口在最上面。因为它们直接有关系,所以它们需要被管理。另外一个原因是:统一的管理有利于机制的实现。举个例子,我们希望在大部分窗口打开的时候都有一个从小变大的效果,那么我们可以通过窗口管理器提供机制去支持这个需求,并且只要改动少量的不需要的这个特性的窗口的代码就可以。这边我们说清楚了为什么要做管理,接下来我们讨论的是管理是不是一个普遍行为。

管理发生不仅仅发生在代码中,也发生在我们生活的方方面面。老板管理高层员工,高层管理中层,中层管理下层员工,这都是管理。同样的,在代码里面,也有很多管理。窗口管理器管理窗口,场景管理器管理场景,所以这种管理就是一种高度的抽象,可以抽象成一个通用的机制。我们现在谈的UI框架,实际上就是一种管理思维,它可以作为窗口管理器管理窗口,也可以作为场景管理器管理场景。我们先过一眼这个框架:

UIManager.ts

class UIManager extends Base{

private tUIObjs:Object = {};

private oCanvas:egret.DisplayoUIObjectContainer;

public constructor(oCanvas:egret.DisplayoUIObjectContainer) {

this.oCanvas = oCanvas;

}

//////////////// 子类关注接口 //////////////////

//ui对象准备开始加载

protected onUIObjBeginLoad(oUIObj:UIView){

}

//私有接口

private destroyoUIObj(oUIObj:UIView){

oUIObj.onPreDestroy();//新增的

oUIObj.onDestroy();

this.oCanvas.removeChild(oUIObj);

}

private loadUIObj(oUIObj:UIView){

let sSkinName = oUIObj.skinXMLName();

if(sSkinName === undefined){//不需要加载皮肤

this.uiSkinReady(oUIObj);

}

else{

oUIObj.addEventListener(Event.SkinReady,

this.uiSkinReadyEvent, this);

oUIObj.loadSkin();

}

}

private uiSkinReadyEvent(event:Core.Event){

this.uiSkinReady(event.data);

}

private uiSkinReady(oUIObj:UIView){

this.oCanvas.addChild(oUIObj);

this.onUIObjBeginLoad(oUIObj);

oUIObj.setLoaded();

oUIObj.registeEventMap();

oUIObj.onPreLoaded();

oUIObj.onLoaded();

oUIObj.onEndLoaded();

}

/////////////// 对外接口 //////////////////

//窗口创建接口

public createUI(sClassname:string, ...args:oUIObject[]):UIView {

let oUIObj = <UIView>this.tUIObjs[sClassname];

if(oUIObj){

return oUIObj;

}

let oClassType:any = egret.getDefinitionByName(sClassname);

oUIObj = new oClassType(args);

this.tUIObjs[sClassname] = oUIObj;

oUIObj.setName(sClassname);

oUIObj.onCreate();

this.loadUIObj(oUIObj);

return oUIObj;

}

public destroyUI(sClassname:string){

let oUIObj = <UIView>this.tUIObjs[sClassname];

if (!oUIObj) {

console.warn("DestroyUI not exist sClassname:" +

sClassname);

return;

}

this.destroyoUIObj(oUIObj);

delete this.tUIObjs[sClassname];

}

public destroyAllUI(){

for(let uiName in this.tUIObjs){

let oUIObj = <UIView>this.tUIObjs[uiName];

this.destroyoUIObj(oUIObj);

}

this.tUIObjs = {}

}

}

UIView.ts

class UIBase extends eui.Component {

private bLoaded:boolean = false;

private bDestroyed = false;

public constructor() {

super();

}

///////////// 子类关注的接口 //////////////

//窗口对象创建时

public onCreate(){}

//所有东西都加载完成

public onLoaded() { }

//销毁时

public onDestroy() { }

//事件映射表

public eventMap() {

return undefined;

}

//皮肤名称

public skinXMLName():string{

return undefined;

}

/////////// 框架子类关注的接口 ///////////

//准备加载

public onPreLoaded() {

}

public onEndLoaded(){

}

/////////// 框架使用的接口 //////////////

public setLoaded(){

this.bLoaded = true;

}

//皮肤加载接口

public loadSkin(){

this.addEventListener(eui.UIEvent.COMPLETE, this.onSkinComplete,this);

this.skinName = "resource/eui_skins/view/" + this.skinXMLName() +".exml";

}

private onSkinComplete(){

this.removeEventListener(eui.UIEvent.COMPLETE,

this.onSkinComplete, this);

let oSkinEvent = egret.Event.create(Event, Event.SkinReady, false);

oSkinEvent.data = this;

this.dispatchEvent(oSkinEvent);

egret.Event.release(oSkinEvent);

}

public registeEventMap(){

let tEventMap = this.eventMap();

if(tEventMap == undefined){

return;

}

for (var i = 0; i < tEventMap.length; ++i) {

let tEventInfo = tEventMap[i];

let oComponent = <eui.Component>tEventInfo[0];

let fFunc:Function = <Function>tEventInfo[1];

let sEventType:string= <string>tEventInfo[2] ||egret.TouchEvent.TOUCH_TAP;

oComponent.addEventListener(sEventType, fFunc, this);

}

}

public setDestroy(){

this.bDestroyed = true;

}

//对外接口

public isValid(){

return !this.bDestroyed;

}

//私有接口

private isLoaded(){

return this.bLoaded;

}

private isDestroyed(){

return this.bDestroyed;

}

}

这个框架的基础部分的接口设计使用前面介绍过的设计方式。总的设计逻辑是这样的:

收集资源加载所必须的api,确定是同步加载还是异步加载。这边使用的loadskin是异步加载。

封装基础时机。基础时机在这边指的是一个被管理对象的创建和销毁以及异步加载完成。最本质的是创建以及销毁,及onCreate和onDestroy接口。异步加载完成时机是因为我们选用了异步加载而额外多出的时机(onLoaded)。在加载资源的各个阶段将这个”消息”合理的告诉子类,对于管理器而言,它告诉了子类准备开始加载子对象(onUIObjBeginLoad),而被管理者UIView告诉子类很多信息,比如onCreate,onDestroy,onLoaded。

加强被管理器的基础功能,抽象出配置化编程基础。

添加自动化平衡处理,防止逻辑错误。

这边解释下上面的点,onCreate和onDestroy在机制这层是一定保证对称出现的。即使继承类出现了逻辑异常,也需要保证它们都会各执行1次。配置化编程是这里面的设计的另外一个核心点,配置化编程指的是我们写代码的时候采用的就像配置一样的方式编码,任何冗余的代码都不需要书写。举个例子,如果你需要监听按钮A和按钮B的两个事件,你这么写:

this.oBtnA.addEventListener(egret.Event.TouchEvent, this.clickBtnA, this);

this.oBtnB.addEventListener(egret.Event.TouchEvent, this.clickBtnB, this);

这里面不同的是oBtnA,clickBtnA 与oBtnB,clickBtnB,其他都是相同的,所以我们用配置化编程改一下,变成了,我们的子类只需要实现:

public eventMap() {

return [

["oBtnA", "clickBtnA" ],

["oBtnB", "clickBtnB" ],

];

}

ok,我们的重复代码就消除了,这就是配置化编程的核心处理。

自动化平衡处理指的是在一个被管理者的生命周期,它要保证在最后能把该释放的都释放了,防止逻辑层泄露。举个例子,一个ui里面使用了一个定时器,通常的写法是在onDestroy里面判断存在就去销毁它,但是很多程序可能会忘记释放。那么这时候我们需要封装定时器管理对象,在准备销毁前去调用该对象的清理函数,保证所有的定时器都能被正确销毁。

这个定时器对象在创建定时器后都会保存住创建后的句柄,如果逻辑层没销毁,那么最后机制会统一帮它清理。自动化平衡处理还能干一些事,比如说异步加载资源回来,逻辑层不需要再去判断窗口是否销毁,回调的接口一定是保证了窗口存在的情况下才调用的。这同样也需要封装一个异步资源管理器对象来实现。

下面我们来看4个东西:

基于上面的代码,如何封装游戏项目的场景管理器和窗口管理器。

实现一个自动化平衡处理。

实现一个窗口

感受机制的威力

封装场景管理器

class SceneManager extends UIManager {

private oCurScene:SceneBase;

public constructor(canvas:egret.DisplayObjectContainer) {

super(canvas);

}

//子类框架实现接口

protected onUIObjBeginLoad(oUIObj:UIView){

if(this.oCurScene != undefined){

this.destroyScene(this.oCurScene.getName());

}

this.oCurScene = <SceneBase>oUIObj;

}

//对外接口

public createScene(sClassname:string, ...args:Object[]):SceneBase{

return <SceneBase>this.createUI(sClassname, ...args);

}

public destroyScene(sClassname:string){

return this.destroyUI(sClassname);

}

public createWindow(sClassname:string, ...args:Object[]):WindowBase{

Core.assert(this.oCurScene != undefined);

return <WindowBase>this.oCurScene.createWindow(sClassname, ...args);

}

public destroyWindow(sClassname:string){

Core.assert(this.oCurScene != undefined);

this.oCurScene.destroyWindow(sClassname);

}

public getWindow(sClassname:string):WindowBase{

return this.oCurScene.getWindow(sClassname);

}

public getCurScene():SceneBase{

return this.oCurScene;

}

}

封装场景:

class SceneBase extends UIView {

private oUIMgr:UIManager;

//子类框架实现接口

public onPreLoaded(){

this.oUIMgr = new UIManager(this);

}

public createWindow(classname:string, ...args:Object[]):WindowBase{

return <WindowBase>this.oUIMgr.createUI(classname, ...args);

}

public destroyWindow(classname:string){

return this.oUIMgr.destroyUI(classname);

}

public getWindow(classname:string):WindowBase{

return <WindowBase>this.oUIMgr.getUI(classname);

}

}

封装窗口:

class WindowBase extends UIView {

public constructor() {

super();

}

//子类框架实现接口

public onPreLoaded() {

//窗口从小变大效果

}

}

上面封装的一些缘由:场景同时只能存在一个,窗口需要一些自定义的效果。因为有了这些特殊性,所以延伸了它们的子类。上面还有一个地方特别要注意,子类关注的东西如果被继承类实现了,那么这个接口在它子类这个级别就变成了框架子类关注,因为子类要在实现的时候调用父类同名接口了。

下面来看一个自动化平衡处理,处理下定时器。

先加强下框架的功能:

在UIManager的代码中加一个代码

//私有接口

private destroyoUIObj(oUIObj:UIView){

oUIObj.onPreDestroy();//新增的

oUIObj.onDestroy();

this.oCanvas.removeChild(oUIObj);

}

在UIView的框架子类关注的接口中加一个

public onPreDestroy(){

}

TimeHelp的实现:

class TimeHelp {

private tHandlers:Array<TimerHandler> = new Array<TimerHandler>;

public addTimeHandle(){

let oTimerHandler = ; //调用引擎时间管理器接口返回oTimerHandler

tHandlers.Add(oTimerHandler);

return oTimerHandler;

}

public rmTimeHandle(oTimerHandler){

if(oTimerHandler.isValid()){

//调用引擎时间管理器接口移除oTimerHandler

}

}

public clear(){

let tHandlers = this.tHandlers;

for(let i = 0; i < tHandlers.length; ++i){

let oTimerHandler = tHandlers[i];

if(oTimerHandler.isValid()){

//调用引擎时间管理器接口移除oTimerHandler

}

}

this.tHandlers.clear();

}}

最后在windowbase里面集成:

class WindowBase extends UIView {

private oTimeHelp:TimeHelp = new TimeHelp();

public constructor() {

super();

}

//子类框架实现接口

public onPreLoaded() {

//窗口从小变大效果

}

public addTimeHandle(...){

//调用系统时间管理器接口返回oTimerHandler

return oTimeHelp.addTimeHandle();

}

public rmTimeHandle(oTimerHandler){

oTimeHelp.rmTimeHandle(oTimerHandler);

}

public onPreDestroy(){

oTimeHelp.clear();

}

}

集成完毕,一切都很安静,这就是防止逻辑犯错的平衡。而UI逻辑层的都调用新封装的接口而不是直接调用底层接口,这样就避免的犯错的可能。

接下来我们看一个窗口的实现,非常简单:

class SettingPanel extends UIView{

private oBtnUIEffect:eui.Image;

private oBtnBg:eui.Image;

//事件映射表

public eventMap() {

return [

["oBtnUIEffect", "changeUIEffect"],

["oBtnBg", "changeBg"]

];

}

//皮肤名称

public skinXMLName():string{

return undefined;

}

public onLoaded() {

//显示默认设置

}

private changeUIEffect(){

//切换ui音效开关

}

private changeBg(){

//切换背景音乐开关

}

}

干干净净,舒舒服服,代码很清晰。

上面主要是讲我们对于我们的使用者,逻辑UI的编写者提供了很大的便利,以及防止他们的一些逻辑错误,但是它的威力远远不止如此。

我们假定我们现在开发了很多个系统,突然策划提出我们需要在按钮的点击响应上面播放点击音效。这时候有个做法是在每个事件响应的回调里面添加一句播放的代码。但是,自从我们有了机制,有了对消息的拦截,我们可以在事件响应的地方做拓展。将UIView的registeEventMap拓展下:

public registeEventMap(){

let tEventMap = this.eventMap();

if(tEventMap == undefined){

return;

}

for (var i = 0; i < tEventMap.length; ++i) {

let tEventInfo = tEventMap[i];

let oComponent = <eui.Component>tEventInfo[0];

let fFunc:Function = <Function>tEventInfo[1];

let sEventType:string= <string>tEventInfo[2] ||

egret.TouchEvent.TOUCH_TAP;

oComponent.addEventListener(sEventType, this.eventHook.bind(fFunc, this),this, fFunc);

}

}

private eventHook(fCallFunc){

//播放声音

fCallFunc();

}

可以看到,一旦有了机制,我们就可以实现全局性的功能,尤其擅长处理大规模需要重复性代码的东西。这也是需要所有窗口都继承自UIView,且由UIManager创建的意义。

相关推荐

一个基于.Net Core遵循Clean Architecture原则开源架构

今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...

AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%

写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...

OneCode低代码平台的事件驱动设计:架构解析与实践

引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...

国内大厂AI插件评测:根据UI图生成Vue前端代码

在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...

AI+低代码技术揭秘(二):核心架构

本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...

GitDiagram用AI把代码库变成可视化架构图

这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...

30天自制操作系统:第六天:代码架构整理与中断处理

1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...

AI写代码越帮越忙?2025年研究揭露惊人真相

近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...

一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具

一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...

5分钟掌握 c# 网络通讯架构及代码示例

以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...

从复杂到优雅:用建造者和责任链重塑代码架构

引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...

低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈

专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...

框架设计并不是简单粗暴地写代码,而是要先弄清逻辑

3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...

大佬用 Avalonia 框架开发的 C# 代码 IDE

AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...

轻量级框架Lagent 仅需20行代码即可构建自己的智能代理

站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...

取消回复欢迎 发表评论: