Unity中的MVC框架

基本概念

MVC全名是Model View Controller
是模型(model)-视图(view)-控制器(controller)的缩写
是一种软件设计规范,用一种业务逻辑、数据、界面显示 分离的方法组织代码
将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

MVC在游戏开发中不是必备的,它主要用于开发游戏UI系统逻辑

前期准备

接下来要实现一个小的UI面板,分别实现不使用MVC框架和使用MVC框架的代码,以此作为对比。

Canvas设置

非MVC框架实现

主面板逻辑

MainPanel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MainPanel : MonoBehaviour
{
    //1.获得控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;

    private static MainPanel panel;
    //2.添加事件
    //3.更新信息
    //4.动态显隐
    //使用静态方法,让NormalMain能够调用
    public static void ShowMe(){
        if(panel == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        panel = obj.GetComponent<MainPanel>();
        }
        //如果隐藏形式是setacive,则显示也要set
        panel.gameObject.SetActive(true);
        //显示完面板 更新
        panel.UpdateInfo();
        
    }
    public static void HideMe(){
        if(panel != null){
            //一. 直接删
            // Destroy(panel.gameObject);
            // panel = null;
            //二. 隐藏
            panel.gameObject.SetActive(false);
        }
    }
    // Start is called before the first frame update 
    void Start()
    {
        //2.添加事件
        btnRole.onClick.AddListener(ClickBtnRole);

    }

   private void ClickBtnRole(){
    //打开角色面板的逻辑
    Debug.Log("按钮点击");
   }

   //3.更新信息
   public void UpdateInfo(){
    //获取玩家数据 更新玩家信息
    //获取玩家数据的方式 1.网络请求  2.Json  3.xml  4.2进制  5.PlayerPrefs公共类
    
    //通过PlayerPrefs来获取本地存储的玩家信息 更新到界面上
    txtName.text = PlayerPrefs.GetString("PlayerName","阿喆不想学习");
    txtLev.text = "LV." + PlayerPrefs.GetInt("PlayerLev",1).ToString();

    txtMoney.text = PlayerPrefs.GetInt("PlayerMoney",999).ToString();
    txtGem.text = PlayerPrefs.GetInt("PlayerGem",888).ToString();
    txtPower.text = PlayerPrefs.GetInt("PlayerPower",10).ToString();
   }
}

NormalMain.cs

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.M)){
            //显示主面板
            MainPanel.ShowMe();
        }
        else if(Input.GetKeyDown(KeyCode.N)){
            //隐藏主面板
            MainPanel.HideMe();
        }
    }

角色面板逻辑

RolePanel.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RolePanel : MonoBehaviour
{
   //1.获得控件
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    private static RolePanel panel;
    //2.添加事件
    //3.更新信息
    //4.动态显隐
    // Start is called before the first frame update 

    public static void ShowMe(){
        if(panel == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        panel = obj.GetComponent<RolePanel>();
        }
        //如果隐藏形式是setacive,则显示也要set
        panel.gameObject.SetActive(true);
        //显示完面板 更新
        panel.UpdateInfo();
        
    }
    public static void HideMe(){
        if(panel != null){
            //一. 直接删
            // Destroy(panel.gameObject);
            // panel = null;
            //二. 隐藏
            panel.gameObject.SetActive(false);
        }
    }

    void Start()
    {
        btnClose.onClick.AddListener(()=>{
            HideMe();
        });

        btnLevUp.onClick.AddListener(()=>{
            //升级就是数据更新
            //这里就是获取本地数据
            int lev = PlayerPrefs.GetInt("PlayerLev",1);
            int hp= PlayerPrefs.GetInt("PlayerHp",100);; 
            int def= PlayerPrefs.GetInt("PlayerDef",10);
            int atk= PlayerPrefs.GetInt("PlayerAtk",20);
            int crit= PlayerPrefs.GetInt("PlayerCrit",20);
            int miss= PlayerPrefs.GetInt("PlayerMiss",10);
            int luck= PlayerPrefs.GetInt("PlayerLuck",40);
            //然后根据升级规则去改变他
            lev += 1;
            hp += lev;
            atk += lev;
            def += lev;
            crit += lev;
            miss += lev;
            luck += lev;
            //存起来
            PlayerPrefs.SetInt("PlayerLev",lev);
            PlayerPrefs.SetInt("PlayerHp",hp);
            PlayerPrefs.SetInt("PlayerAtk",atk);
            PlayerPrefs.SetInt("PlayerDef",def);
            PlayerPrefs.SetInt("PlayerCrit",crit);
            PlayerPrefs.SetInt("PlayerMiss",miss);
            PlayerPrefs.SetInt("PlayerLuck",luck);

            //同步更新面板上的数据
            UpdateInfo();
            //更新主面板的数据
            MainPanel.Panel.UpdateInfo();
        });
    }

    //更新信息
    public void UpdateInfo(){
        txtLev.text = "LV." + PlayerPrefs.GetInt("PlayerLev",1).ToString();

        txtHp.text = PlayerPrefs.GetInt("PlayerHp",100).ToString();
        txtAtk.text = PlayerPrefs.GetInt("PlayerAtk",20).ToString();
        txtDef.text = PlayerPrefs.GetInt("PlayerDef",10).ToString();
        txtCrit.text = PlayerPrefs.GetInt("PlayerCrit",20).ToString();
        txtMiss.text = PlayerPrefs.GetInt("PlayerMiss",10).ToString();
        txtLuck.text = PlayerPrefs.GetInt("PlayerLuck",40).ToString();
       
    }
}

MainPanel更新

    public static MainPanel Panel{
        get{
            return panel;
        }
    } 

    private void ClickBtnRole(){
    //打开角色面板的逻辑
        RolePanel.ShowMe();
   }

MVC框架实现

Model数据脚本

using System.Collections;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 作为一个唯一的数据模型
/// 一般情况下 要不自己是个单例模式对象 
/// 要么自己存在在一个单例模式中
/// </summary>
public class PlayerModel
{
    //数据内容
    private string playerName;
    //用属性是为了能让外部得到它但不能改变他
    public string PlayerName{
        get{
            return playerName;
        }
    }
    private int lev;
    public int Lev{
        get{
            return lev;
        }
    }
    private int money;
    public int Money{
        get{
            return money;
        }
    }
    private int gem;
    public int Gem{
        get{
            return gem;
        }
    }
    private int power;
    public int Power{
        get{
            return power;
        }
    }
    private int hp;
    public int Hp{
        get{
            return hp;
        }
    }
    private int atk;
    public int Atk{
        get{
            return atk;
        }
    }
    private int def;
    public int Def{
        get{
            return def;
        }
    }
    private int crit;
    public int Crit{
        get{
            return crit;
        }
    }
    private int miss;
    public int Miss{
        get{
            return miss;
        }
    }
    private int luck;
    public int Luck{
        get{
            return luck;
        }
    }

    //通知外部更新的事件
    //通过它来与外部建立联系 而不是直接获取外部的面板
    private event UnityAction<PlayerModel> updateEvent;

    //在外部第一次获取这个数据 如何获取
    //通过单例模式 来达到数据的唯一性 和数据获取
    private static PlayerModel data = null;

    public static PlayerModel Data{
        get{
            if(data == null){
                data = new PlayerModel();
                data.Init();
            }
            return data;
        }

    }


    //数据相关的操作
    //   初始化
    public void Init(){
        playerName = PlayerPrefs.GetString("PlayerName","阿喆不想学习");
        lev = PlayerPrefs.GetInt("PlayerLev",1);
        money = PlayerPrefs.GetInt("PlayerMoney",9999);
        gem = PlayerPrefs.GetInt("PlayerGem",8888);
        power = PlayerPrefs.GetInt("PlayerPower",99);
        hp= PlayerPrefs.GetInt("PlayerHp",100);; 
        def= PlayerPrefs.GetInt("PlayerDef",10);
        atk= PlayerPrefs.GetInt("PlayerAtk",20);
        crit= PlayerPrefs.GetInt("PlayerCrit",20);
        miss= PlayerPrefs.GetInt("PlayerMiss",10);
        luck= PlayerPrefs.GetInt("PlayerLuck",40);
    }
    //   更新  在这里是升级
    public void LevUp(){
        //升级 改变内容
        lev += 1;
        hp += lev;
        atk += lev;
        def += lev;
        crit += lev;
        miss += lev;
        luck += lev;
        //改变后保存
        SaveData();
    }
    //   保存
    public void SaveData(){
        //把这些数据内容 存储到本地
        PlayerPrefs.SetString("PlayerName",playerName);
        PlayerPrefs.SetInt("PlayerLev",lev);
        PlayerPrefs.SetInt("PlayerMoney",money);
        PlayerPrefs.SetInt("PlayerGem",gem);
        PlayerPrefs.SetInt("PlayerPower",power);
      
        PlayerPrefs.SetInt("PlayerHp",hp);
        PlayerPrefs.SetInt("PlayerAtk",atk);
        PlayerPrefs.SetInt("PlayerDef",def);
        PlayerPrefs.SetInt("PlayerCrit",crit);
        PlayerPrefs.SetInt("PlayerMiss",miss);
        PlayerPrefs.SetInt("PlayerLuck",luck);

        UpdateInfo();
    }

    public void AddEventListener(UnityAction<PlayerModel> function){
        updateEvent += function;
    }

    public void RemoveEventListener(UnityAction<PlayerModel> function){
        updateEvent -= function;
    }

    //通知外面更新数据的方法
    private void UpdateInfo(){
        //找到对应的 使用数据的脚本 去更新数据
        if(updateEvent != null){
            updateEvent(this);
        }
    }
}

View界面脚本

MainView.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MainView : MonoBehaviour
{
    //1.找控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;
    public Button btnSkill;

    //2.提供面板更新的方法给外部
    public void UpdateInfo(PlayerModel data){
        txtName.text = data.PlayerName;
        txtLev.text = "LV." + data.Lev;
        txtMoney.text = data.Money.ToString();
        txtGem.text = data.Gem.ToString();
        txtPower.text = data.Power.ToString();
    }
}

RoleView.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RoleView : MonoBehaviour
{
    //1.找控件
    
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    //2.提供面板更新的相关方法给外部
    public void UpdateInfo(PlayerModel data){
        txtLev.text = "LV." + data.Lev;
        txtHp.text = data.Hp.ToString();
        txtAtk.text = data.Atk.ToString();
        txtDef.text = data.Def.ToString();
        txtCrit.text = data.Crit.ToString();
        txtMiss.text = data.Miss.ToString();
        txtLuck.text = data.Luck.ToString();
    }
}

Controller业务逻辑

MainController

using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;

/// <summary>
/// Controller要处理的东西 就是业务逻辑
/// </summary>
public class MainController : MonoBehaviour
{
    //能够在Controller中得到界面才行
    private MainView mainView;
    //面板之间的交互都是通过Controller来实现,我们不想让mainView也变成静态被其它访问,
    //因此可以设置个静态的 Controller,因为Controller也要被外部访问
    private static MainController controller = null;
    public static MainController Controller{
        get{
            return controller;
        }
    }
    //1.界面的显隐
     public static void ShowMe(){
        if(controller == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        controller = obj.GetComponent<MainController>();
        }
        //如果隐藏形式是setacive,则显示也要set
        controller.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(controller != null){

            controller.gameObject.SetActive(false);
        }
    }

    private void Start(){
        //获取同样挂载在一个对象上的 view脚本
        mainView = this.GetComponent<MainView>();
        //第一次更新
        mainView.UpdateInfo(PlayerModel.Data);

        //2.界面 事件的监听 来处理对应的业务逻辑
        mainView.btnRole.onClick.AddListener(ClickRoleBtn);

        //PlayerModel.Data.AddEventListener(mainView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }
    
    private void ClickRoleBtn(){
        RoleController.ShowMe();
    }
    //3. 界面的更新
    private void UpdateInfo(PlayerModel data)
    {
        if(mainView != null){
            mainView.UpdateInfo(data);
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}


RoleController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;

public class RoleController : MonoBehaviour
{
    private RoleView roleView;

    private  static RoleController controller = null;
    public static RoleController Controller{
        get{
            return controller;
        }
    }

     public static void ShowMe(){
        if(controller == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        controller = obj.GetComponent<RoleController>();
        }
        //如果隐藏形式是setacive,则显示也要set
        controller.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(controller != null){
            controller.gameObject.SetActive(false);
        }
    }
    void Start()
    {
        roleView = this.GetComponent<RoleView>();
        //第一次更新面板
        roleView.UpdateInfo(PlayerModel.Data);

        roleView.btnClose.onClick.AddListener(ClickCloseBtn);
        roleView.btnLevUp.onClick.AddListener(ClickLevUpBtn);
        //PlayerModel.Data.AddEventListener(roleView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }

    private void ClickCloseBtn(){
        HideMe();
    }

    private void ClickLevUpBtn(){
        //通过数据模块 进行升级 达到数据改变
        PlayerModel.Data.LevUp();
        
    }
    // Update is called once per frame
    private void UpdateInfo(PlayerModel data)
    {
        if(roleView != null){
            roleView.UpdateInfo(data);
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

对比与总结

好处

1.各司其职,互不干涉 --编程思路更清晰
2.有利开发中的分工 -- 多人协同开发时,同步并行
3.有利于组件重用  -- 项目换皮时,功能变化小时,提高开发效率

坏处

1.增加了程序文件的体量  -- 脚本由一变三
2.增加了结构的复杂性  --对于不清楚MVC原理的人不友好
3.效率相对较低     -- 对象之间的相互跳转,始终伴随着一定开销

扩展

MVC的美中不足

M和V之间存在着联系,也就是数据和界面之间存在着耦合性,当数据结构改变时会牵扯界面逻辑随之改动。
在MVC中当需求变化时,需要维护的对象数量会增加

如这一段

改变了PlayerModel中的变量的话,则MainView里的这个函数可能也要调整。

MVX

数据和界面是必备的内容
我们可以通过改变X元素来优化原本的MVC
也就是改变联系和处理M(数据)和V(界面)的方式

MVP:切断View和Model的耦合,让Presenter处理一切
MVVM:MVP的升级版,让ViewModel和V进行双向数据绑定,更新VM等同于更新V,反之同理
MVE:用EventCenter事件中心来分发消息

学习MVX的目的
不要拘泥于框架结构和设计模式要找到一个适合自己项目
一个稳定的,有序的,能满足项目需求的实现方式

MVP

全称为模型(Model)-视图(View)一主持人(Presenter)

Model提供数据,View负责界面,Presenter负责逻辑的处理

它是MVC的一种变式,是针对MVC中M和V存在耦合的优化

与MVC的区别:

在MVC中View会直接从Model中读取数据而不是通过 Controller
而在MVP中View并不直接使用Model,它们之间的通信是通过Presenter来进行的,所有的交互都发生在Presenter内部。

同样是上面的项目

MVP_MainView.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MVP_MainView : MonoBehaviour
{
     //1.找控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;
    public Button btnSkill;

    // //2.提供面板更新的方法给外部
    // public void UpdateInfo(string name, int lev, int money, int gem, int power){
    //     txtName.text = name;
    //     txtLev.text = "LV." + lev;
    //     txtMoney.text = money.ToString();
    //     txtGem.text = gem.ToString();
    //     txtPower.text = power.ToString();
    // }
}

MVP_RoleView.cs

public class MVP_RoleView : MonoBehaviour
{
 //1.找控件
    
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    //2.提供面板更新的相关方法给外部
    //方法可选 到时候可以直接在P里面通过访问控件 去修改

}

MainPresenter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainPresenter : MonoBehaviour
{
 //能够在Presenter中得到界面才行
    private MVP_MainView mainView;
    //面板之间的交互都是通过Controller来实现,我们不想让mainView也变成静态被其它访问,
    //因此可以设置个静态的 Controller,因为Controller也要被外部访问
    private static MainPresenter presenter = null;
    public static MainPresenter Presenter{
        get{
            return presenter;
        }
    }
    //1.界面的显隐
     public static void ShowMe(){
        if(presenter == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        presenter = obj.GetComponent<MainPresenter>();
        }
        //如果隐藏形式是setacive,则显示也要set
        presenter.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(presenter != null){

            presenter.gameObject.SetActive(false);
        }
    }

    private void Start(){
        //获取同样挂载在一个对象上的 view脚本
        mainView = this.GetComponent<MVP_MainView>();
        //第一次更新
        //mainView.UpdateInfo(PlayerModel.Data);
        //通过P自己的更新方法来更新
        UpdateInfo(PlayerModel.Data);
        //2.界面 事件的监听 来处理对应的业务逻辑
        mainView.btnRole.onClick.AddListener(ClickRoleBtn);

        //PlayerModel.Data.AddEventListener(mainView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }
    
    private void ClickRoleBtn(){
        //RoleController.ShowMe();
        RolePresenter.ShowMe();
    }
    //3. 界面的更新
    private void UpdateInfo(PlayerModel data)
    {
        if(mainView != null){
            //mainView.UpdateInfo(data);
            //以前是把数据M传到V中去更新,现在全部由P来做
            mainView.txtName.text = data.PlayerName;
            mainView.txtLev.text = "LV." + data.Lev;
            mainView.txtMoney.text = data.Money.ToString();
            mainView.txtGem.text = data.Gem.ToString();
            mainView.txtPower.text = data.Power.ToString();
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

RolePresenter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RolePresenter : MonoBehaviour
{
    private MVP_RoleView roleView;

    private  static RolePresenter presenter = null;
    public static RolePresenter Presenter{
        get{
            return presenter;
        }
    }

     public static void ShowMe(){
        if(presenter == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        presenter = obj.GetComponent<RolePresenter> ();
        }
        //如果隐藏形式是setacive,则显示也要set
        presenter.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(presenter != null){
            presenter.gameObject.SetActive(false);
        }
    }
    void Start()
    {
        roleView = this.GetComponent<MVP_RoleView>();
        //第一次更新面板
        UpdateInfo(PlayerModel.Data);

        roleView.btnClose.onClick.AddListener(ClickCloseBtn);
        roleView.btnLevUp.onClick.AddListener(ClickLevUpBtn);
        //PlayerModel.Data.AddEventListener(roleView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }

    private void ClickCloseBtn(){
        HideMe();
    }

    private void ClickLevUpBtn(){
        //通过数据模块 进行升级 达到数据改变
        PlayerModel.Data.LevUp();
        
    }
    // Update is called once per frame
    private void UpdateInfo(PlayerModel data)
    {
        if(roleView != null){
            //直接在p中得到V界面的控件,断开M和V的联系
            roleView.txtLev.text = "LV." + data.Lev;
            roleView.txtHp.text = data.Hp.ToString();
            roleView.txtAtk.text = data.Atk.ToString();
            roleView.txtDef.text = data.Def.ToString();
            roleView.txtCrit.text = data.Crit.ToString();
            roleView.txtMiss.text = data.Miss.ToString();
            roleView.txtLuck.text = data.Luck.ToString();
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

MVP同样由缺点,Presenter中的逻辑很多。

但是之后逻辑修改也只用在Presenter中。

MVVM

全称为模型(Model)-视图(View)-视图模型(ViewModel)
Model提供数据,View负责界面,ViewModel负责逻辑的处理
MVVM的由来是MVP(Model-View-Presenter)模式与WPF结合应用时发展演变过来的一种新型框架

数据绑定

将一个用户界面元素(控件)的属性 绑定到 一个类型(对象)实例上的某个属性的方法。

如果开发者有一个MainViewMode类型的实例,那么他就可以把MainViewMode的“Lev”属性绑定到一个Ul中Text的“Text”属性上。“绑定”了这2个属性之后,对Text的Text属性的更改“传播”MainViewMode的Lev属性,而对MainViewMode的Lev属性的更改同样会“传播”到Text的Text属性

MVVM在Unity中水土不服

因为View对象始终由我们来书写,没有UI配置文件(如WPF中的XAML),要想实现传播需要事件/委托,很麻烦。

硬要在Unity中实现MVVM,需要写三模块,并且还要对V和VM进行数据绑定,工作量大,好处也不够明显

Unity的第三方MVVM框架
Loxodon Framework
https://github.com/vovgou/loxodon-framework
uMVVM
https://github.com/MEyes/uMVVM

MVE

全称为模型(Model)-视图(View)-事件中心(EventCenter)
Model提供数据,View负责界面,EventCenter负责数据传递

好处:

利用事件中心的观察者模式
让M和V层的之间的关系更加灵活多变
减少了目前数据层的负载
将数据层事件全部交由事件中心处理

总结

铁打的M和V,流水的X
数据和界面是必备的内容
我们可以通过改变X元素来优化原本的MVC
也就是改变联系和处理M(数据)和V(界面)的方式

不要拘泥于框架结构和设计模式要找到一个适合自己项目的
一个稳定的,有序的,能满足项目需求的实现方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/666640.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tinycudann安装

在安装完torch等 直接运行下面的指令会出现错误 pip install githttps://github.com/NVlabs/tiny-cuda-nn/#subdirectorybindings/torch大部分错误是下面的 大概看了一下都是因为虚拟环境里面的include文件下缺少文件&#xff0c;将之前的一些.h文件全部复制过来在执行上面的…

TransmittableThreadLocal原理

1、原理 TransmittableThreadLocal&#xff08;简称TTL&#xff09;是阿里巴巴开源的一个Java库&#xff0c;用于解决线程池中线程本地变量传递的问题。其底层原理主要是基于Java的ThreadLocal机制并对其进行扩展&#xff0c;以支持在父子线程间以及线程池中任务切换时&#x…

Java实现顺序表

Java顺序表 前言一、线性表介绍常见线性表总结图解 二、顺序表概念顺序表的分类顺序表的实现throw具体代码 三、顺序表会出现的问题 前言 推荐一个网站给想要了解或者学习人工智能知识的读者&#xff0c;这个网站里内容讲解通俗易懂且风趣幽默&#xff0c;对我帮助很大。我想与…

Adobe AntiCC 简化版 安装教程

Adobe AntiCC 简化版 安装教程 原文地址&#xff1a;https://blog.csdn.net/weixin_48311847/article/details/139277743

TensorFlow Playground神经网络演示工具使用方法详解

在现代机器学习领域,神经网络无疑是一个重要的研究方向。然而,对于许多初学者来说,神经网络的概念和实际操作可能显得相当复杂。幸运的是,TensorFlow Playground 提供了一个交互式的在线工具,使得我们可以直观地理解和实验神经网络的基本原理。在这篇博客中,我们将详细介…

解读vue3源码-2

提示&#xff1a;看到我 请让滚去学习 vue3编译模版的提升 文章目录 vue3编译模版的提升静态节点提升补丁标志和block的使用附录&#xff1a; template explorer可以将我们的源模版转化成渲染函数代码&#xff0c;vue2中就有&#xff0c;而Vue3 template explorer 功能更加丰富…

开发nfc读卡器应用出现报错Unhandled Exception: SCARD_E_NO_SERVICE

使用flutter开发ACR122U的nfc读卡器的时候&#xff0c;报错&#xff1a; [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Exception: Error while establing context. Reason: SCARD_E_NO_SERVICE #0 PCSCBinding._checkAndThrow (package:fl…

Vue 框选区域放大(纯JavaScript实现)

需求&#xff1a;长按鼠标左键框选区域&#xff0c;松开后放大该区域&#xff0c;继续框选继续放大&#xff0c;反向框选恢复原始状态 实现思路&#xff1a;根据鼠标的落点&#xff0c;放大要显示的内容&#xff08;内层盒子&#xff09;&#xff0c;然后利用水平偏移和垂直偏…

ABB码垛机器人IRB260通讯板维修

ABB码垛机器人在现代制造业中发挥着重要作用&#xff0c;而机器人通讯板维修对于确保机器人的正常运行至关重要。 通讯板是ABB码垛机器人与控制系统之间进行数据传输的桥梁。它负责接收控制系统的指令&#xff0c;并将机器人的运行数据反馈给控制系统。如果通讯板出现故障&…

ESP32开发板定义硬串口

ESP32 的默认串口 UART序号Rx PINTx PIN是否可用UART0GPIO3GPIO1是UART1GPIO9GPIO10是&#xff0c; 但与SPI flash相关联需要重新定义UART2GPIO16GPIO17是 下面我们定义2、4GPIO引脚为串口1&#xff1a; #include <HardwareSerial.h> HardwareSerial S1(1); 初始化 …

C语言 | Leetcode C语言题解之第120题三角形最小路径和

题目&#xff1a; 题解&#xff1a; int minimumTotal(int** triangle, int triangleSize, int* triangleColSize) {int f[triangleSize];memset(f, 0, sizeof(f));f[0] triangle[0][0];for (int i 1; i < triangleSize; i) {f[i] f[i - 1] triangle[i][i];for (int j …

【VTKExamples::PolyData】第五十四期 SelectVisiblePoints

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例SelectVisiblePoints,并解析接口vtkSelectVisiblePoints,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动…

[Linux系统编程]文件IO

一.系统调用 什么是系统调用? 只有系统调用(系统函数)才能进入内核空间&#xff0c;库函数也是调用系统函数&#xff0c;才得以访问底层。 系统调用由操作系统实现并提供给外部应用程序的编程接口。是应用程序同系统之间数据交互的桥梁。 换句话说&#xff0c;系统调用就是操…

yolov5-ros模型结合zed2相机部署在 Ubuntu系统

前言 本篇文章主要讲解yolov5-ros模型结合zed2相机进行实时检测&#xff0c;经改进实现了红绿灯检测&#xff0c;并输出检测类别与置信度&#xff01; 目录 一、环境配置二、zed2驱动安装三、yolov5-ros功能包配置四、运行官方权重文件四、运行自己权重文件 一、环境配置 1、…

14-alert\confirm\prompt\自定义弹窗

一、认识alert\confirm\prompt 下图依次是alert、confirm、prompt&#xff0c;先认清楚长什么样子&#xff0c;以后遇到了就知道如何操作了。 二、alert操作 先用driver.switch_to.alert方法切换到alert弹出框上&#xff1b;可以用text方法获取弹出的文本信息&#xff1b;acce…

【介绍下运维,什么是运维?】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

C++ | Leetcode C++题解之第120题三角形最小路径和

题目&#xff1a; 题解&#xff1a; class Solution { public:int minimumTotal(vector<vector<int>>& triangle) {int n triangle.size();vector<int> f(n);f[0] triangle[0][0];for (int i 1; i < n; i) {f[i] f[i - 1] triangle[i][i];for (…

RK3588+FPGA+AI高性能边缘计算盒子,应用于视频分析、图像视觉等

搭载RK3588&#xff08;四核 A76四核 A55&#xff09;&#xff0c;CPU主频高达 2.4GHz &#xff0c;提供1MB L2 Cache 和 3MB L3 &#xff0c;Cache提供更强的 CPU运算能力&#xff0c;具备6T AI算力&#xff0c;可扩展至38T算力。 产品规格 系统主控CPURK3588&#xff0c;四核…

【QEMU中文文档】1.1 支持的构建平台

本文由 AI 翻译&#xff08;ChatGPT-4&#xff09;完成&#xff0c;并由作者进行人工校对。如有任何问题或建议&#xff0c;欢迎联系我。联系方式&#xff1a;jelin-shoutlook.com。 原文&#xff1a;Supported build platforms — QEMU documentation QEMU 旨在支持在多个主机…

Webrtc支持HEVC之FFMPEG支持HEVC编解码(一)

一、前言 Webrtc使用的FFMPEG(webrtc\src\third_party\ffmpeg)和官方的不太一样,使用GN编译,各个平台使用了不一样的配置文件 以Windows为例,Chrome浏览器也类似 二、修改配置文件 windows:chromium\config\Chrome\win\x64 其他平台: chromium\config\Chrome\YOUR_SYS…