游戏框架搭建

使用框架的目标:低耦合,高内聚,表现和数据分离
耦合:对象,类的双向引用,循环引用
内聚:相同类型的代码放在一起
表现和数据分离:需要共享的数据放在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();}}#endregionprivate IOCContainer mContainer = new IOCContainer();// 留给子类注册模块protected abstract void Init();// 提供一个注册模块的 APIpublic void Register<T>(T instance){MakeSureArchitecture();mArchitecture.mContainer.Register<T>(instance);}// 提供一个获取模块的 APIpublic 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.mzph.cn/news/717071.shtml

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

相关文章

跨平台指南:在 Windows 和 Linux 上安装 OpenSSL 的完整流程

Windows安装 一&#xff1a;找到安装包&#xff0c;双击即可 https://gitee.com/wake-up-again/installation-package.git 二&#xff1a;按照提示&#xff0c;一步一步来&#xff0c;就可以啦 三&#xff1a;此界面意思是&#xff0c;是否想向创作者捐款&#xff0c;自己视情…

2024最新搭建Mybatis配置教程【超详细】

为什么要学习mybatis 首先要弄清楚什么是mybatis&#xff1f;我们为什么要学mybatis 学习MyBatis可以帮助开发人员更高效地进行数据库操作&#xff0c;提高开发效率&#xff0c;并且可以使得应用程序更具可维护性和性能优势。 我们知道Java程序操作数据库是通过jdbc与数据库进…

蓝桥杯——矩形拼接

矩形拼接 题目分析 对于一个矩形而言&#xff0c;我可以把它横着放&#xff0c;而可以把它竖着放&#xff0c;比如下图&#xff0c; 3个矩形的拼接情况可以通过在纸上画图模拟出来&#xff0c;情况有以下三种 ​ 图1 图3是4条边&#xff0c;即四边形。观察一下什么时候会是四…

IO(Linux)

文件系统 前言1. 回顾关于C文件部分函数2. 一些文件知识的共识3. 相对路径4. fwrite中的\0 一、文件描述符fd1. 概念2. 系统调用① open 和 close② write③ read 和 lseek 3. 缺省打开的fd 二、重定向1. 原理2. 系统调用dup23. stdout和stderr的区别4. 进程替换和原来进程文件…

【计算机考研】408学到什么程度才能考130?

408考130要比考研数学考130难的多 我想大部分考过408的考生都是这么认为的。408的难点在于他涉及的范围太广了&#xff0c;首先如果你要备考408&#xff0c;你要准备四门课程&#xff0c;分别是数据结构&#xff0c;计算机组成原理&#xff0c;操作系统和计算机网络。 这四门…

企业计算机服务器中了360勒索病毒如何解密,360后缀勒索病毒处理流程

对于众多的企业来说&#xff0c;企业的数据是企业发展的核心&#xff0c;越来越多的企业开始注重企业的数据安全问题&#xff0c;但随着网络技术的不断发展与应用&#xff0c;网络黑客的攻击加密手段也在不断升级。近期&#xff0c;云天数据恢复中心接到多家企业的求助&#xf…

设计模式—命令模式:探索【命令模式】的奥秘与应用实践!

命令模式 命令模式是一种行为设计模式&#xff0c;它的主要目的是将请求封装成一个对象&#xff0c;从而使得请求的发送者和接收者之间进行解耦。 在命令模式中&#xff0c;命令被封装为一个对象&#xff0c;包含了需要执行的操作以及执行这些操作所需的所有参数。 命令的发送者…

OpenGuass 之 where 1 = 0 处理流程代码走读

一. 前言 在OpenGuass中&#xff0c;如果where 条件中包含where 1 0 等固定为否条件的查询语句&#xff0c;在生成执行计划的时候&#xff0c;执行计划是BaseResult类型&#xff0c;此类型的执行计划不会进行物理数据扫描&#xff0c;如下所示&#xff1a; 对于非固定为否条件&…

【论文阅读】多传感器SLAM数据集

一、M2DGR 该数据集主要针对的是地面机器人&#xff0c;文章正文提到&#xff0c;现在许多机器人在进行定位时&#xff0c;其视角以及移动速度与车或者无人机有着较大的差异&#xff0c;这一差异导致在地面机器人完成SLAM任务时并不能直接套用类似的数据集。针对这一问题该团队…

latex中\documentclass[preprint,review,12pt]{elsarticle}的详细解释

在LaTeX中&#xff0c;\documentclass 是一个命令&#xff0c;用于指定文档所使用的文档类。文档类定义了文档的总体结构、格式和样式。elsarticle 是一个常用的文档类&#xff0c;它主要用于在Elsevier出版的期刊上提交论文。 详细解释 \documentclass[preprint,review,12pt…

Autosar Appl介绍

AUTOSAR架构中的应用层 AUTOSAR 应用层构成AUTOSAR 架构中的最顶层,被认为对所有车辆应用至关重要。AUTOSAR 标准使用“组件”概念指定应用层实现。 在谈论应用层实现时,应该考虑的三个最重要的部分是: AUTOSAR 应用软件组件这些组件的 AUTOSAR 端口AUTOSAR 端口接口 AUTOS…

浙江大学主办!2024年第7届信息通信与信号处理国际会议( ICICSP2024)征稿开启!

会议官网 IEEE | ICICSP 2024 学术会议查询-学术会议交流服务平台-爱科会易 (uconf.com)​www.uconf.com/

OpenChat:性能高达105.7%,第一个超越ChatGPT的开源模型?

OpenChat&#xff1a;性能高达105.7%&#xff0c;第一个超越ChatGPT的开源模型&#xff1f; 前几天开源模型第一还是是Vicuna-33B、WizardLM&#xff0c;这不又换人了。对于开源模型的风起云涌&#xff0c;大家见怪不怪&#xff0c;不断更新的LLM榜单似乎也没那么吸引人了。 …

在springboot项目中调用通义千问api多轮对话并实现流式输出

官网文档 阿里灵积提供了详细的官方文档 如何实现多轮对话 官方文档中提到只需要把每轮对话中返回结果添加到消息管理器中&#xff0c;就可以实现多轮对话。本质上就是将历史对话再次发送给接口。 如何实现流式输出 官方文档中提出使用streamCall()方法就可以实现流式输出&…

ViT的若干细节

之前只看了ViT的大概结构&#xff0c;具体的模型细节和代码实现知之甚少。随着ViT逐渐成为CV领域的backbone&#xff0c;有必要重新审视下。 patch -> token 为了将图片处理成序列格式&#xff0c;很自然地想到将图片分割成一个个patch&#xff0c;再把patch处理成token。 …

Linux学习:初始Linux

目录 1. 引子&#xff1a;1.1 简述&#xff1a;操作系统1.2 学习工具 2. Linux操作系统中的一些基础概念与指令2.1 简单指令2.2 ls指令与文件2.3 cd指令与目录2.4 文件目录的新建与删除指令2.5 补充指令1&#xff1a;2.6 文件编辑与拷贝剪切2.7 文件的查看2.8 时间相关指令2.9 …

【网站项目】202物流管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

不会代码的时候,如何使用Jmeter完成接口测试

1.接口测试简介 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 2.接口测试流程 接口测试的…

2024.3.1 小项目

1、机械臂 #include <myhead.h> #define SER_IP "192.168.125.32" //服务器端IP #define SER_PORT 8888 //服务器端端口号#define CLI_IP "192.168.68.148" //客户端IP #define CLI_PORT 9999 /…

Python matplotlib

目录 1、安装 matplotlib 2、绘制折线图 修改标签文字和线条粗细 校正图形 3、绘制散点图 绘制单点 绘制一系列点 自动计算数据 删除数据点的轮廓 自定义颜色 使用颜色映射 自动保存图表 4、随机漫步 创建 RandomWalk() 类 选择方向 绘制随机漫步图 给点着色 …