游戏框架搭建

news2024/9/24 7:02:48

使用框架的目标:低耦合,高内聚,表现和数据分离
耦合:对象,类的双向引用,循环引用
内聚:相同类型的代码放在一起
表现和数据分离:需要共享的数据放在Model里

对象之间的交互一般有三种

  1. 方法调用,A持有B才能调用B的方法
  2. 委托或回调,A持有B才能注册B的委托,尽量避免嵌套调用
  3. 消息或事件,A不需要持有B

A调用B的方法,A就必须持有B,形成单向引用关系,为了避免耦合,B不应该引用A,如果B想调用A的方法,使用委托或回调。
总结:父节点调用子节点可以直接方法调用,子节点通知父节点用委托或事件,跨模块通信用事件

模块化一般有三种

  1. 单例,例如: Manager Of Managers
  2. IOC,例如: Extenject,uFrame的 Container,StrangelOC的绑定等等
  3. 分层,例如: MVC、三层架构、领域驱动分层等等

交互逻辑和表现逻辑

在这里插入图片描述
以计数器为例,用户操作界面修改数据叫交互逻辑,当数据变更之后或者初始化时,从Model里查询数据在View上显示叫表现逻辑
交互逻辑:View -> Model
表现逻辑:Model -> View

很多时候,我们不会真的去用 MVC 开发架构,而是使用表现(View)和数据(Model)分离这样的思想,我们只要知道 View 和 Model 之间有两种逻辑,即交互逻辑 和 表现逻辑,我们就不用管中间到底是 Controller、还是 ViewModel、还是 Presenter。只需要想清楚交互逻辑 和 交互逻辑如何实现的就可以了。

View和Model怎样交互比较好,或者说交互逻辑和表现逻辑怎样实现比较好?

<1> 直接方法调用,表现逻辑是在交互逻辑完成之后主动调用,伪代码如下

public class CounterViewController : MonoBehaviour
{
    void Start()
    {
        transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑
                CounterModel.Count++;
                
                // 表现逻辑
                UpdateView();
            });

        transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑
                CounterModel.Count--;
                
                // 表现逻辑
                UpdateView();
            });
        
        // 表现逻辑
        UpdateView();
    }

    void UpdateView()
    {
        transform.Find("CountText").GetComponent<Text>().text = CounterModel.Count.ToString();
    }
}

public static class CounterModel
{
    public static int Count = 0;
}

<2> 使用委托

public class CounterViewController : MonoBehaviour
{
    void Start()
    {
        // 注册
        CounterModel.OnCountChanged += OnCountChanged;

        transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count++;
            });

        transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count--;
            });
        
        OnCountChanged(CounterModel.Count);
    }

    // 表现逻辑
    private void OnCountChanged(int newCount)
    {
        transform.Find("CountText").GetComponent<Text>().text = newCount.ToString();
    }

    private void OnDestroy()
    {
        // 注销
        CounterModel.OnCountChanged -= OnCountChanged;
    }
}

public static class CounterModel
{
    private static int mCount = 0;

    public static event Action<int> OnCountChanged ;
    
    public static int Count
    {
        get => mCount;
        set
        {
            if (value != mCount)
            {
                mCount = value;
                OnCountChanged?.Invoke(value);
            }
        }
    }
}

<3> 使用事件,事件管理器写法差不多,这里忽略具体实现

public class CounterViewController : MonoBehaviour
{
    void Start()
    {
        // 注册
        EventManager.Instance.RegisterEvent(EventId, OnCountChanged);

        transform.Find("BtnAdd").GetComponent<Button>()
            .onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count++;
            });

        transform.Find("BtnSub").GetComponent<Button>()
            .onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count--;
            });
        
        OnCountChanged();
    }

    // 表现逻辑
    private void OnCountChanged()
    {
        transform.Find("CountText").GetComponent<Text>().text = CounterModel.Count.ToString();
    }

    private void OnDestroy()
    {
        // 注销
        EventManager.Instance.UnRegisterEvent(EventId, OnCountChanged);
    }
}

public static class CounterModel
{
    private static int mCount = 0;
    
    public static int Count
    {
        get => mCount;
        set
        {
            if (value != mCount)
            {
                mCount = value;
                
                // 触发事件
                EventManager.Instance.FireEvent(EventId);
            }
        }
    }
}

比较上面3种实现方式,当数据量很多的时候,使用第1种方法调用会写很多重复代码调用,代码臃肿,容易造成疏忽,使用委托或事件代码更精简,当数据变化时会自动触发表现逻辑,这就是所谓的数据驱动。

所以表现逻辑使用委托或事件更合适,如果是单个数值变化,用委托的方式更合适,比如金币、分数、等级、经验值等等,如果是颗粒度较大的更新用事件比较合适,比如从服务器拉取了一个任务列表数据,然后任务列表数据存到了Model

BindableProperty

上面的Model类,每新增一个数据就要写一遍类似的代码,很繁琐,我们使用泛型来简化代码

public class BindableProperty<T> where T : IEquatable<T>
{
    private T mValue;

    public T Value
    {
        get => mValue;
        set
        {
            if (!mValue.Equals(value))
            {
                mValue = value;
                OnValueChanged?.Invoke(value);
            }
        }
    }

    public Action<T> OnValueChanged;
}

BindableProperty 也就是可绑定的属性,是 数据 + 数据变更事件 的合体,它既存储了数据充当 C# 中的 属性这样的角色,也可以让别的地方监听它的数据变更事件,这样会减少大量的样板代码

public class CounterViewController : MonoBehaviour
{
    void Start()
    {
        // 注册
        CounterModel.Count.OnValueChanged += OnCountChanged;

        transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count.Value++;
            });

        transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>
            {
                // 交互逻辑:这个会自动触发表现逻辑
                CounterModel.Count.Value--;
            });
        
        OnCountChanged(CounterModel.Count.Value);
    }

    // 表现逻辑
    private void OnCountChanged(int newValue)
    {
        transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();
    }

    private void OnDestroy()
    {
        // 注销
        CounterModel.Count.OnValueChanged -= OnCountChanged;
    }
}

public static class CounterModel
{
    public static BindableProperty<int> Count = new BindableProperty<int>()
    {
        Value = 0
    };
}

总结:

  • 自顶向下的逻辑使用方法调用
  • 自底向上的逻辑使用委托或事件,Model和View是底层和上层的关系,所以用委托或事件更合适

Command

实际的开发中交互逻辑的代码是很多的,随着功能需求越来越多,Controller的代码会越来越臃肿,解决办法是引入命令模式(Command),命令模式参考另一篇博客:Unity常用设计模式
先定义一个接口

public interface ICommand
{
    void Execute();
}

添加一个命令,实现数据加一操作,注意这里是用 struct 实现的,而不是用的 class,这是因为游戏里边的交互逻辑有很多,如果每一个都用去 new 一个 class 的话,会造成很多性能消耗,比如 new 一个对象所需要的寻址操作、比如对象回收需要的 gc 等等,而 struct 内存管理效率要高很多

public struct AddCountCommand : ICommand
{
    public void Execute()
    {
        CounterModel.Count.Value++;
    }
}

实现数据减一操作

public struct SubCountCommand : ICommand
{
    public void Execute()
    {
        CounterModel.Count.Value--;
    }
}

更新交互逻辑的代码

    public class CounterViewController : MonoBehaviour
    {
        void Start()
        {
            // 注册
            CounterModel.Count.OnValueChanged += OnCountChanged;

            transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>
                {
                    // 交互逻辑
                    new AddCountCommand().Execute();
                });

            transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>
                {
                    // 交互逻辑
                    new SubCountCommand().Execute();
                });
            
            OnCountChanged(CounterModel.Count.Value);
        }

        // 表现逻辑
        private void OnCountChanged(int newValue)
        {
            transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();
        }

        private void OnDestroy()
        {
            // 注销
            CounterModel.Count.OnValueChanged -= OnCountChanged;
        }
    }

    public static class CounterModel
    {
        public static BindableProperty<int> Count = new BindableProperty<int>()
        {
            Value = 0
        };
    }

使用 Command 符合读写分离原则(Comand Query Responsibility Segregation),简写为 CQRS ,这个概念在 StrangeIOC、uFrame、PureMVC、Loxodon Framework 都有实现,而在微服务领域比较火的 DDD(领域驱动设计)的实现一般也会实现 CQRS。它是一种软件架构模式,旨在将应用程序的读取和写入操作分离为不同的模型。在CQRS中,写操作通常由命令模型(Command Model)来处理,它负责处理业务逻辑和状态更改。而读操作则由查询模型(Query Model)来处理,它专门用于支持数据查询和读取展示。

Command 模式就是逻辑的调用和执行是分离的,我们知道一个方法的调用和执行是不分离的,因为一旦你调用方法了,方法也就执行了,而 Command 模式能够做到调用和执行在空间和时间上是能分离的。

空间分离的方法就是调用的地方和执行的地方放在两个文件里。
时间分离的方法就是调用的之后,Command 过了一点时间才被执行。

Command 分担 Controller 的交互逻辑,由于有了调用和执行分离这个特点,所以我们可以用不同的数据结构去组织 Command 调用,比如列表,队列,栈

在这里插入图片描述
底层系统层是可以共享给别的展现层使用的,切换表现层非常方便,表现层到系统层用 Command 改变底层系统的状态(数据),系统层通过事件或者委托通知表现层,在通知的时候可以推送数据,也可以让表现层收到通知后自己去查询数据。

模块化

使用单例

单例比静态类好一点就是其生命周期相对可控,而且访问单例对象比访问静态类多了一点限制,也就是需要通过 Instance 获取

每个模块继承 Singleton

public class Singleton<T> where T : class
{
    public static T Instance
    {
        get
        {
            if (mInstance == null)
            {
                // 通过反射获取构造
                var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
                // 获取无参非 public 的构造
                var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);

                if (ctor == null)
                {
                    throw new Exception("Non-Public Constructor() not found in " + typeof(T));
                }

                mInstance = ctor.Invoke(null) as T;
            }

            return mInstance;
        }
    }

    private static T mInstance;
}

问题:单例没有访问限制,容易造成模块之间互相引用,关系混乱

IOC容器

IOC 容器可以理解为是一个字典,这个字典以 Type 为 key,以对象即 Instance 为 value,IOC 容器最少有两个核心的 API,即根据 Type 注册实例,根据 Type 获取实例

public class IOCContainer
{
    /// <summary>
    /// 实例
    /// </summary>
    public Dictionary<Type, object> mInstances = new Dictionary<Type, object>();

    /// <summary>
    /// 注册
    /// </summary>
    /// <param name="instance"></param>
    /// <typeparam name="T"></typeparam>
    public void Register<T>(T instance)
    {
        var key = typeof(T);

        if (mInstances.ContainsKey(key))
        {
            mInstances[key] = instance;
        }
        else
        {
            mInstances.Add(key,instance);
        }
    }

    /// <summary>
    /// 获取
    /// </summary>
    public T Get<T>() where T : class
    {
        var key = typeof(T);
        
        object retObj;
        
        if(mInstances.TryGetValue(key,out retObj))
        {
            return retObj as T;
        }

        return null;
    }
}

下面是一个简单的示例,IOC 容器创建,注册实际应当写在游戏初始化时,这里为了方便演示都写在一起了

public class IOCExample : MonoBehaviour
{
    void Start()
    {
        // 创建一个 IOC 容器
        var container = new IOCContainer();
        
        // 注册一个蓝牙管理器的实例
        container.Register(new BluetoothManager());
        
        // 根据类型获取蓝牙管理器的实例
        var bluetoothManager = container.Get<BluetoothManager>();
        
        //连接蓝牙
        bluetoothManager.Connect();
    }

    public class BluetoothManager
    {
        public void Connect()
        {
            Debug.Log("蓝牙连接成功");
        }
    }
}

为了避免样板代码,这里创建一个抽象类

/// <summary>
/// 架构
/// </summary>
public abstract class Architecture<T> where T : Architecture<T>, new()
{
    #region 类似单例模式 但是仅在内部课访问
    private static T mArchitecture = null;
    
    // 确保 Container 是有实例的
    static void MakeSureArchitecture()
    {
        if (mArchitecture == null)
        {
            mArchitecture = new T();
            mArchitecture.Init();
        }
    }
    #endregion

    private IOCContainer mContainer = new IOCContainer();

    // 留给子类注册模块
    protected abstract void Init();

    // 提供一个注册模块的 API
    public void Register<T>(T instance)
    {
        MakeSureArchitecture();
        mArchitecture.mContainer.Register<T>(instance);
    }

    // 提供一个获取模块的 API
    public static T Get<T>() where T : class
    {
        MakeSureArchitecture();
        return mArchitecture.mContainer.Get<T>();
    }
}

子类注册多个模块

public class PointGame : Architecture<PointGame>
{
    // 这里注册模块
    protected override void Init()
    {
        Register(new GameModel1());
        Register(new GameModel2());
        Register(new GameModel3());
        Register(new GameModel4());
    }
}

使用 IOC 容器的目的是增加模块访问的限制

除了可以用来注册和获取模块,IOC 容器一般还会有一个隐藏的功能,即:注册接口模块

public class IOCExample : MonoBehaviour
{
    void Start()
    {
        // 创建一个 IOC 容器
        var container = new IOCContainer();
        
        // 根据接口注册实例
        container.Register<IBluetoothManager>(new BluetoothManager());
        
        // 根据接口获取蓝牙管理器的实例
        var bluetoothManager = container.Get<IBluetoothManager>();
        
        //连接蓝牙
        bluetoothManager.Connect();
    }
    
    /// <summary>
    /// 定义接口
    /// </summary>
    public interface IBluetoothManager
    {
        void Connect();
    }

    /// <summary>
    /// 实现接口
    /// </summary>
    public class BluetoothManager : IBluetoothManager
    {
        public void Connect()
        {
            Debug.Log("蓝牙连接成功");
        }
    }
}

抽象-实现 这种形式注册和获取对象的方式是符合依赖倒置原则的。
依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。依赖倒置原则是 SOLID 中的字母 D。

这种设计的好处:

  • 接口设计与实现分成两个步骤,接口设计时可以专注于设计,实现时可以专注于实现。
  • 实现是可以替换的,比如一个接口叫 IStorage,其实现可以是 PlayerPrefsStorage、EdtiroPrefsStorage,等切换时候只需要一行代码就可以切换了。
  • 比较容易测试(单元测试等)
  • 降低耦合。
接口的显式实现
public interface ICanSayHello
{
    void SayHello();
    void SayOther();
}

public class InterfaceDesignExample : MonoBehaviour, ICanSayHello
{
    /// <summary>
    /// 接口的隐式实现
    /// </summary>
    public void SayHello()
    {
        Debug.Log("Hello");
    }

    /// <summary>
    /// 接口的显式实现,不能写访问权限关键字
    /// </summary>
    void ICanSayHello.SayOther()
    {
        Debug.Log("Other");
    }
    
    void Start()
    {
        // 隐式实现的方法可以直接通过对象调用
        this.SayHello();
        
        // 显式实现的接口不能通过对象调用
        // this.SayOther() // 会报编译错误
        
        // 显式实现的接口必须通过接口对象调用
        (this as ICanSayHello).SayOther();
    }
}

当需要实现多个签名一致的方法时,可以通过接口的显式声明来区分到底哪个方法是属于哪个接口的
利用接口的显示实现,子类想要调用必须先转成接口,这样就增加了调用显式实现的方法的成本,所以可以理解为这个方法被阉割了

分层

前面使用 Command 分担了 Controller 的交互逻辑的部分逻辑,并不是所有的交互逻辑都适合用 Command 来分担的,还有一部分交互逻辑是需要交给 System 层来分担。这里 System 层在概念等价于游戏的各个管理类 Manager。
Command 是没有状态的,有没有状态我们可以理解为这个对象需不需要维护数据,因为 Command 类似于是一个方法,只要调用然后执行一次就可以不用了,所以 Command 是没有状态的

梳理一下当前的架构

  • 表现层:即 ViewController 或者 MonoBehaviour 脚本等,负责接受用户的输入,当状态变化时更新表现
  • System 层:系统层,有状态,在多个表现层共享的逻辑,负责即提供 API 又有状态的对象,比如网络服务、蓝牙服务、商城系统等,也支持分数统计、成就系统这种硬编码比较多又需要把代码放在一个位置的需求。
  • Model 层:管理数据,有状态,提供数据的增删改查。
  • Utility 层:工具层,无状态,提供一些必备的基础工具,比如数据存储、网络链接、蓝牙、序列化反序列化等。

表现层改变 System、Model 层级的状态用 Command,System 层 和 Model 层 通知 表现层用事件,委托或 BindableProeprty,表现层查询状态时可以直接获取 System 和 Model 层

每个层级都有一些规则:

表现层

  • 可以获取 System
  • 可以获取 Model
  • 可以发送 Command
  • 可以监听 Event

系统层

  • 可以获取其他 System
  • 可以获取 Model
  • 可以监听,发送 Event
  • 可以获取 Utility

数据层

  • 可以获取 Utility
  • 可以发送 Event

工具层

  • 啥都干不了,可以集成第三方库,或者封装 API

除了四个层级,还有一个核心概念就是 Command

Command

  • 可以获取 System
  • 可以获取 Model
  • 可以获取 Utility
  • 可以发送 Event
  • 可以发送其他 Command

贫血模型和充血模型

在这里插入图片描述
我们有一个 User 对象,伪代码如下

public class User
{
    public string Name {get;set;}
    public int Age {get;set;}
    public string Id {get;set;}
    public string NickName {get;set;
    public float Weight {get;set;}
}

总共有五个属性,但是在表现层的界面中,只需要显示三个属性,即:姓名 Name、年龄 Age、和 Id。
表现层查询一个用户数据的时候,返回了一个 完整的 User 对象,这种数据流向模型叫做贫血模型。就是表现层需要用到的数据结果给了一整个未经过筛选的数据对象过来。

在这里插入图片描述
定义了一个 UserInfo 类,伪代码如下;

public class UserInfo
{
    public string Name {get;set;}
    public int Age {get;set;}
    public string Id {get;set;}
}

充血模型就是表现层需要哪些数据,就刚好返回哪些数据

充血模型 比 贫血模型 需要做跟多的工作,写更多的代码,甚至还有跟多的性能消耗。
但是在越大规模的项目中 充血模型 的好处就会更加明显。因为充血模型,可以让我们的代码更精确地描述业务,会提高代码的可读性,而贫血模型,会让我们的数据逐渐趋于混乱。

参考

凉鞋 《框架搭建 决定版》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XUbuntu22.04之显示实时网速(二百一十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【字符串】马拉车(Manacher)算法

本篇文章参考&#xff1a;比较易懂的 Manacher&#xff08;马拉车&#xff09;算法配图详解 马拉车算法可以求出一个字符串中的最长回文子串&#xff0c;时间复杂度 O ( n ) O(n) O(n) 因为字符串长度的奇偶性&#xff0c;回文子串的中心可能是一个字符&#xff0c;也可能是…

智慧草莓基地:Java与SpringBoot的技术革新

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

ue4.27 发现 getRandomReachedLocation 返回 false

把这个玩意儿删掉&#xff0c;重启工程&#xff0c;即可 如果还不行 保证运动物体在 volum 内部&#xff0c;也就是绿色范围内确保 project setting 里面的 navigation system 中 auto create navigation data 是打开的(看到过博客说关掉&#xff0c;不知道为啥) 如果还不行&…

STM32学习和实践笔记(1): 装好了的keil μVision 5

2019年3月在淘宝上买了这块STM32的开发板&#xff0c;学了一段时间后就丢下了&#xff0c;今天重新捡起来&#xff0c;决定好好学习、天天向上。 对照教程&#xff0c;今天先把keil5装上了。 装的过程有以下几点值得记录下&#xff1a; 1&#xff09;用注册机时&#xff0c;…

【数据结构】B树

1 B树介绍 B树&#xff08;英语&#xff1a;B-tree&#xff09;&#xff0c;是一种在计算机科学自平衡的树&#xff0c;能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作&#xff0c;都在对数时间内完成。B树&#xff0c;概括来说是一个一般化的…

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码

波斯猫 6页面 宠物动物 长毛猫 HTML5 带背景音乐 JS图片轮播特效 滚动文字 鼠标经过图片 JS时间代码 注册表单 宠物网页成品 海量学生网页成品 个人博客 人物明星 城市家乡 旅游景点 美食特产 购物电商 公司企业 学校大学 科普教育 宠物动物 鲜花花卉 植物水果 茶叶咖啡 健康生…

【前端寻宝之路】学习如何使用HTML实现简历展示和填写

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-iJ3Ou0qMGFVaqVQq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Google Dremel和parquet的复杂嵌套数据结构表征方法解析

转载请注明出处。作者&#xff1a;archimekai 核心参考文献&#xff1a; Dremel: Interactive Analysis of Web-Scale Datasets 文章目录 引言复杂嵌套数据结构的无损表征问题Dremel论文中提出的表征方法parquet备注 引言 Dremel是Google的交互式分析系统。Google大量采用prot…

LabVIEW石油钻机提升系统数字孪生技术

LabVIEW石油钻机提升系统数字孪生技术 随着数字化、信息化、智能化的发展&#xff0c;石油钻采过程中的石油钻机数字化技术提升成为了提高钻井效率、降低生产成本的重要途径。基于中石油云平台提供的数据&#xff0c;采用数字孪生技术&#xff0c;对石油钻机提升系统进行数字化…

设计模式(十三)抽象工厂模式

请直接看原文:设计模式&#xff08;十三&#xff09;抽象工厂模式_抽象工厂模式告诉我们,要针对接口而不是实现进行设计。( )-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- …

Some collections -- 2024.3

一、TensorFlow Android (dataset: Mnist) We used TensorFlow to define and train our machine learning model, which can recognize handwritten numbers, called a number classifier model in machine learning terminology. We transform the trained TensorFlow mod…

pytest-allure报告生成

pytest生成allure报告步骤&#xff1a; 下载allure&#xff0c;配置allure报告的环境变量&#xff1a;把allure-2.13.7\bin 配置到环境变量path路径 验证&#xff1a;在dos窗口和pycharm窗口分别验证&#xff1a;allure –version 2. 生成临时的json报告 在pytest.ini配置文…

挑战杯 基于深度学习的中文情感分类 - 卷积神经网络 情感分类 情感分析 情感识别 评论情感分类

文章目录 1 前言2 情感文本分类2.1 参考论文2.2 输入层2.3 第一层卷积层&#xff1a;2.4 池化层&#xff1a;2.5 全连接softmax层&#xff1a;2.6 训练方案 3 实现3.1 sentence部分3.2 filters部分3.3 featuremaps部分3.4 1max部分3.5 concat1max部分3.6 关键代码 4 实现效果4.…

【k8s管理--可视化界面】

1、可视化界面的软件 kubernetes的可视化软件有以下这些kubernetes dashboard&#xff1a;https://github.com/kubernetes/dashboardkubesphere官网&#xff1a; https://kubesphere.io/zh/rancher 官网&#xff1a; https://www.rancher.cn/kuboard 官网&#xff1a; https:/…

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示应用

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1638键盘数码管模块概述TM1638键盘数码管…

Matlab 多项式插值(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 由于对曲线拟合有些兴趣,这里就找了一些资料从最基本的方法来看一下曲线拟合的效果: 二、实现代码 % **********

【Git】深入理解 Git 分支合并操作:git merge dev 命令详解

深入理解 Git 合并操作&#xff1a;git merge dev 命令详解 摘要&#xff1a;本文将深入探讨 Git 中的合并操作&#xff0c;以及如何使用 git merge dev 命令将dev 分支的修改合并到当前分支&#xff08;假设当前分支为main 分支&#xff09;中。通过详细的解释和示意图&#x…

【笔记】【电子科大 离散数学】 3.谓词逻辑

谓词引入 因为含变量的语句&#xff08;例如x > 3&#xff09;不是命题&#xff0c;无法进行逻辑推理。 为了研究简单命题句子内部的逻辑关系&#xff0c;我们需要对简单命题进行分解&#xff0c;利用个体词&#xff0c;谓词和量词来描述它们&#xff0c;并研究个体与总体…

django MTV 静态文件js的添加方式,以及怎么优化js的加载

django MTV 静态文件js的添加方式,以及怎么优化js的加载 1&#xff1a;怎么添加js 2&#xff1a;怎么优化js的加载 django MTV 需要用到的js时&#xff0c;使用以下方式 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF…