当前位置: 首页 > news >正文

Android MVC、MVP、MVVM三种架构的介绍和使用。

写在前面:现在随便出去面试Android APP相关的工作,面试官基本上都会提问APP架构相关的问题,用Java、kotlin写APP的话,其实就三种架构MVC、MVP、MVVM,MVC和MVP高度相似,区别不大,MVVM则不同,引入了新的JetPack工具:ViewModel、LiveData、DataBinding,导入了“View和数据双向绑定的概念”。搞Android APP的必须把这三种架构搞清楚、搞透彻。

  正式开始码字前,我们要先清楚两个词:高内聚、低耦合。相信计算机和相关专业的同学肯定都在老师的嘴里听说过这两个词,我们写Android APP为什么要用架构,其实就是为了实现这两个词代表的含义。
  高内聚:Java、kotlin都是面向对象编程的语言,高内聚就是要求每个类的功能精简,类里面的每个方法精简。最好就是每个类、每个方法相对独立,对外的依赖少,功能明确。
  低耦合:追求高内聚的结果必然就是低耦合,低耦合说的就是代码的不同功能模块之间,没有绝对的依赖关系,不是谁离开谁就运行不下去那种。不能出现搭积木拆了一块剩下的全塌了这种情况。
  高内聚、低耦合的目的最终就是为了项目代码方便维护,后续方便功能扩展。代码架构就是为了更方便的实现高内聚、低耦合目标的代码组成方式,使用了之后你的代码就像用收纳盒规整过一样。当然高内聚、低耦合只是一个指导思想,在实际开发中我们不可能处处都能完美做到,但必须作为一个追求的目标。
  下面依次介绍MVC、MVP、MVVM,搞清楚它们之间的联系和区别,以及为什么会演变出新的架构。

一、MVC

  MVC主要分为三个部分,Model数据层、View视图层、Controller控制层,Model和View不直接联系,它们之间以Controller作为纽带。下面举一个简单的例子进行说明。

1.1 Model:负责数据处理

  用Android Studio创建一个普通的项目。创建一个UserModel类用来模拟处理数据,当然写得很简单。

// Model - 保存用户数据
public class UserModel {private String name;// 设置用户名字public void setName(String name) {this.name = name;}// 获取用户名字public String getName() {return this.name;}
}

1.2 View:负责显示UI、更新UI和接收用户输入事件

  View视图层简单来说就是Activity、Fragment、Dialog这些负责承载视图的组件,方便理解就直接把它看成Activity吧。MainActivity中我们这样写:代码都比较简单可以直接看懂。

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private EditText nameInput; // 输入框private Button submitButton; // 提交按钮private TextView welcomeMessage; // 显示欢迎消息private UserController controller; // Controller@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化界面组件nameInput = findViewById(R.id.nameInput);submitButton = findViewById(R.id.submitButton);welcomeMessage = findViewById(R.id.welcomeMessage);// 初始化 Controllercontroller = new UserController(this);// 按钮点击事件submitButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 获取用户输入并通过 Controller 处理controller.onSubmitButtonClicked();}});}// 显示欢迎消息public void showWelcomeMessage(String message) {welcomeMessage.setText(message);}// 获取用户输入的名字public String getUserInput() {return nameInput.getText().toString();}
}

1.3 Controller:负责将用户输入的数据传递给 Model,并通过 View 更新界面

  创建一个Controller类:

// Controller - 处理业务逻辑
public class UserController {private MainActivity view; // Viewprivate UserModel model;  // Modelpublic UserController(MainActivity view) {this.view = view;this.model = new UserModel();}// 处理提交按钮点击事件public void onSubmitButtonClicked() {// 从 View 获取用户输入String name = view.getUserInput();// 将输入保存到 Model 中model.setName(name);// 创建欢迎消息并通过 View 显示String welcomeMessage = "Hello, " + model.getName() + "!";view.showWelcomeMessage(welcomeMessage);}
}

1.4 总结

  上述的代码我们实现了一个最简单的MVC架构,MVC的核心思想就是让View视图层和Model数据处理层完全解耦分离,中间通过Controller控制层链接,视图层只负责更新数据,不像以前把很多逻辑都塞到Activity中,导致Activity文件臃肿。可以看到Controller同时持有了Model、View的实例对象。MVC架构可以用下面这张图做一个形象的表示:
在这里插入图片描述

点击下载 Android MVC架构示例Demo https://github.com/xuhao120833/MVC

二、MVP

  MVP包含三个部分:Model、View、Presenter。和MVC相比用Presenter替代了Controller,在MVC中Controller同时持有了Model、View的实例对象,起到中间人的作用,但是这种形式的缺点在于,换一个View就得新建一个Controller,Controller无法复用,大大增加了代码量,于是MVP更进一步,Presenter持有的不再是Model、View的实例对象,而是一个接口引用,这样一来Presenter就可以得到复用,进一步解耦了Model和Presenter的关系、View和Presenter的关系。

  我们举一个模拟登录界面的例子来说明MVP架构。

2.1 创建接口

  Android Studio新建一个名称为MVP的项目,之后新建如下三个Java接口:ILoginModel、LoginCallback、LoginView。
  ILoginModel接口用于不同的Model实现。

package com.htc.mvp;public interface ILoginModel {// 定义登录方法void login(String username, String password, LoginCallback callback);
}

  LoginCallback接口,在调用ILoginModel.login方法时实现的回调。

package com.htc.mvp;// 回调接口,用于通知登录结果
public interface LoginCallback {void onSuccess(String user);void onFailure(String error);
}

  LoginView接口用于不同的View去实现。

package com.htc.mvp;public interface LoginView {// 显示加载动画void showLoading();// 隐藏加载动画void hideLoading();// 登录成功时显示消息void showLoginSuccess(String message);// 登录失败时显示错误void showLoginError(String error);
}

2.2 LoginModel实现ILoginModel接口

package com.htc.mvp;import android.os.Handler;
import android.os.Looper;public class LoginModel implements ILoginModel {@Overridepublic void login(String username, String password, LoginCallback callback) {// 模拟网络延迟2秒new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {@Overridepublic void run() {if ("admin".equals(username) && "123456".equals(password)) {callback.onSuccess("欢迎您, " + username + "!");} else {callback.onFailure("用户名或密码错误");}}}, 2000);}
}

2.3 MainActivity实现LoginView接口

package com.htc.mvp;import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity implements LoginView {private LoginPresenter presenter;private EditText etUsername;private EditText etPassword;private Button btnLogin;private ProgressBar progressBar;@SuppressLint("MissingInflatedId")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);etUsername = findViewById(R.id.etUsername);etPassword = findViewById(R.id.etPassword);btnLogin = findViewById(R.id.btnLogin);progressBar = findViewById(R.id.progressBar);// 使用构造函数注入 LoginModel 实例presenter = new LoginPresenter(this, new LoginModel());btnLogin.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {String username = etUsername.getText().toString().trim();String password = etPassword.getText().toString().trim();presenter.performLogin(username, password);}});}@Overridepublic void showLoading() {progressBar.setVisibility(View.VISIBLE);}@Overridepublic void hideLoading() {progressBar.setVisibility(View.GONE);}@Overridepublic void showLoginSuccess(String message) {Toast.makeText(this, message, Toast.LENGTH_LONG).show();}@Overridepublic void showLoginError(String error) {Toast.makeText(this, error, Toast.LENGTH_LONG).show();}
}

2.4 实现LoginPresenter

package com.htc.mvp;public class LoginPresenter {private LoginView view;private ILoginModel model;// 通过构造方法传入 ILoginModel 的实例,可以在外部进行依赖注入public LoginPresenter(LoginView view, ILoginModel model) {this.view = view;this.model = model;}// 执行登录操作public void performLogin(String username, String password) {if (username == null || username.isEmpty() || password == null || password.isEmpty()) {view.showLoginError("用户名和密码不能为空");return;}view.showLoading();model.login(username, password, new LoginCallback() {@Overridepublic void onSuccess(String user) {view.hideLoading();view.showLoginSuccess(user);}@Overridepublic void onFailure(String error) {view.hideLoading();view.showLoginError(error);}});}
}

2.5 总结

  可以看到MVP模式中,Model、View中用到的方法都被抽象到接口中了,而Presenter只持有了Model、View的接口引用,保证了Presenter可以得到复用。MVP架构可以用下面这张图做一个形象的表示:创建Presenter的时候传的是View、Model的实例,但是Presenter保存的却是View、Model的接口引用,这就保证了Presenter可以调用不同的View和Model,保证了Presenter的复用,这是MVP相较于MVC进步最大的地方。还有一个不常提的优势是,由于Presenter持有的是接口引用,就很方便进行单元测试,不用创建真的View、Model,可以使用Mockito或类似的库模拟传入Presenter进行测试。
在这里插入图片描述
注:图中的虚线表示通过接口进行方法调用。

点击下载Android MVP架构示例Demo:https://github.com/xuhao120833/MVP

三、MVVM

  MVVM架构有三大要素:Model、View、ViewModel。相较于MVC、MVP,MVVM迎来了很大的变化,ViewModel不直接持有View的引用或者实例对象,而是通过DataBinding或者LiveData.observe来更新UI。MVVM的学习难度更高,引入了三个新的技术:ViewModel、DataBinding、LiveData。在正式举例介绍MVVM之前,我们必须对这三个新伙伴,做一个介绍。

3.1 ViewModel:

  ViewModel是JetPack androidx.lifecycle仓库中的组件,存在的意义主要是帮助开发者有效保存UI界面数据,看它所属于的仓库路径包含lifecycle,就知道它和“生命周期”强相关。上述解释很抽象,必须举个例子理解一下。
  Android开发者都知道,当系统语言、屏幕方向(横屏)、主题等发生变化时,会触发当前Activity重建重新执行Activity,如果你把和UI相关的数据放在Activity中,数据就会被重新赋值导致数据丢失。倘若你的UI界面复杂,数据很多,那么数据丢失带来的结果将是毁灭性的。Android传统的开发也给我们提供了保存数据的方法,但是很简陋、不实用,只适合保存简单键值对,如下所示:以下示例为伪代码。

	@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (savedInstanceState != null) {// 恢复数据front = savedInstanceState.getInt("front", -1);rear = savedInstanceState.getInt("rear", -1);}}@Overrideprotected void onSaveInstanceState(Bundle outState) {//onSaveInstanceState() → onPause() → onStop() → onDestroy()super.onSaveInstanceState(outState);//保存数据到 Bundle,避免系统设置如语言改变时,首页midlleApp显示的位置复位。if (circularQueue != null) {outState.putInt("front", circularQueue.front);                // 保存头指针outState.putInt("rear", circularQueue.rear);                // 保存尾指针Log.d(TAG,"onSaveInstanceState 保存头尾指针 front "+circularQueue.front+" rear"+circularQueue.rear);}}

  注:Bundle savedInstanceState就是Android原生提供的保存数据的方法。用起来也很简单,就是当系统属性发生变化时,Activity被重建前会先回调到onSaveInstanceState方法,如果有需要保存的数据就用Bundle outState进行保存,Activity重新执行到onCreate时又通过Bundle savedInstanceState把数据取出来使用,达到防止数据丢失的作用。
  但是通过Bundle来保存数据也有致命的缺陷:1、它只能保存下面的类型:简单的基本类型键值对;List、ArrayList实例对象,T必须实现Parcelable接口,也就是必须是可序列化、反序列化的类。2、Bundle最大只能保存1MB的数据,很小,超过限制会崩溃。就是因为有这些缺点,我们必须引入ViewModel,它可以保存大量数据、几乎所有类型数据都能保存、生命周期和Activity/Fragment无关。 Bundle 和ViewModel区别如下图:
    在这里插入图片描述
总结:为什么要用ViewModel? ——》数据放在ViewModel中,ViewModel有独立的生命周期,Activity/Fragment意外被销毁时,和它没有关系,它可以保存大量数据,囊括几乎所有类型,完美解决了Bundle savedInstanceState的致命缺陷。

3.2 LiveData

  LiveData也是JetPack androidx.lifecycle仓库下提供的一个组件,和生命周期这个概念也是强相关,当然这次不是它自己的什么周期,而是观察者的生命周期。这话听起来很拗口,我们接下来慢慢说。
  LiveData是用来保存数据的,它最核心的设计思想是“观察者模式”,也就是它可以被Activty/Fragment观察,当它保存的数据发生变化时自动通知所有的观察者更新UI。说到这里有同学就问了:“那这和普通的观察者模式也没区别呀?只不过代替码农封装了通知观察者的过程,把这个过程隐藏了而已。”LiveData不仅如此,它最牛的地方来了:可以多个Activity/Fragment同时观察一个LiveData数据,LiveData数据发生变化时,会根据传进来的LifecycleOwner,提前获悉所有观察者的生命周期状态,只有处在 “活跃期”的,它才会通知,已经销毁的还会自动解绑。 完全不用程序员自己操心,避免了Activity/Fragment已经被销毁,但是依然是观察者,数据改变依然需要通知的内存泄漏。下面我总结一下LiveData的特点:
  <1> 观察者生命周期感知:上面说了,它只会通知“活跃期”的观察者更新UI,那么怎么定义活跃期?很简单,就是用户能用眼睛看到的就是处在活跃期的观察者。
  <2>自动处理线程:处理耗时任务传统的写法是开一个线程后台处理,处理完之后如果数据需要用来更新UI,那么还得手动切换到主线程,比如runOnUiThread。LiveData就不需要这么麻烦,它提供了两个更新数据的方法,一个setValue用于在主线程直接更新数据,立马通知活跃观察者;一个postValue用于在其它线程更新数据,postValue的工作逻辑是,把数据更新的任务自动放到主线程的工作队列中,主线程执行到了这个任务再去更新数据——》通知活跃的观察者。
  <3>避免内存泄:LiveData 是弱引用的,这意味着当 Activity 或 Fragment 被销毁时,它的观察者会自动被解除,不会导致内存泄漏。你不需要手动移除观察者。
  <4>常见类型:常见的有LiveData和MutableLiveData两种,LiveData中的数据只读,MutableLiveData的数据可读可写。
  <5>支持保存几乎所有数据类型
  说了这么多,接下来用kotlin看看一个最简单的Livedata如何使用:

注:以下代码用伪代码展示//ViewModel类中使用MutableLiveData保存数据
class UserViewModel : ViewModel() {val user = MutableLiveData<User>()val userName = MutableLiveData<String>()fun update_user() {user.value = User(id = 1, name = userName.value.toString(), age = 30)//kotlin中的.value = 就是setValue在主线程更新LiveData数据的意思}. . . . . .
}//MainActivity 中观察userName的变化
class MainActivity : AppCompatActivity() {companion object {private const val TAG = "MainActivity"}private val userViewModel: UserViewModel by viewModels() // ✅ 移到类内部private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 观察 LiveData 数据变化userViewModel.userName.observe(this) { userName ->// 这里可以处理一些更新逻辑Log.d(TAG,"userViewModel.user.observe binding.livedata.setText"+userName)userViewModel.update_user()}}. . . . . .
}

  总结:综合看下来LiveData更像是一个托管工具人,它够强大也很好用,专门用来更新UI,自动管理线程,避免内存泄漏,是一个半自动化工具。

3.3 DataBinding

  如果说LiveData是一个半自动化工具的话,那么DataBinding就是全自动化工具。强如LiveData最后也需要通过binding.livedata.setText(userName+" Livedata使用")这种形式来更新UI,DataBinding直接把userViewModel.userName.observe(this)这种观察到变化再更新的模式直接抛弃了,它直接到layout布局文件给UI组件绑定数据。
其它特点和LiveData高度相似,也是只会更新活跃期的Activity/Fragment。我们需要注意的是,DataBinding的绑定方式分为两种:
  <1> 双向绑定:数据变化会导致UI变化,UI变化也会导致数据变化,最典型的例子就是EditText。
  <2> 单向绑定:只有数据变化才会导致UI变化,反过来却不行。下面举个kotlin例子介绍如何使用:
  想要使用DataBinding,第一步需要在build.gradl.kts中打开databinding开关:

android {. . . . . .buildFeatures {dataBinding = true}
}

  第二步,去layout布局文件中,结合ViewModel(用一般的数据类也行,这里只是用ViewModel结合举例)绑定UI和数据。@={ 双向绑定,@{ 单向绑定

<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="viewModel"type="com.htc.mvvm_kotlin.UserViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><!-- 双向绑定:EditTextViewModel 中的 userName 绑定 --><EditTextandroid:id="@+id/editTextName"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.userName}" /><!-- 单向绑定:TextView 显示 userName --><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="@{viewModel.userName}" /><!-- 显示其他信息 --><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="@{`User Name: ` + viewModel.user.name}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="@{`User ID: ` + viewModel.user.id}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="@{`Age: ` + viewModel.user.age}" /><TextViewandroid:id="@+id/livedata"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="LiveData" /></LinearLayout>
</layout>

  第三步,Activty/Fragment中加载使用。

class MainActivity : AppCompatActivity() {companion object {private const val TAG = "MainActivity"}private val userViewModel: UserViewModel by viewModels() // ✅ 移到类内部private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)// 绑定 ViewModelbinding.viewModel = userViewModelbinding.lifecycleOwner = this// 观察 LiveData 数据变化userViewModel.userName.observe(this) { userName ->// 这里可以处理一些更新逻辑Log.d(TAG,"userViewModel.user.observe binding.livedata.setText"+userName)binding.livedata.setText(userName+" Livedata使用")userViewModel.update_user()}}
}

  总结:DataBinding是一个绑定UI、自动更新UI的工具,很省事,中间过程几乎都省略了,但是我个人是不推荐使用DataBinding双向绑定的,因为如果一旦UI更新出错,那么将是致命的,报错信息很少,几乎无法排错。使用起来也容易出错,导致编译不过。

3.4 总结

  MVVM引入了LiveData、DataBinding、ViewModel这些强大的工具,旨在进一步解耦代码,ViewModel不直接持有View层的实例对象或者引用,而是通过DataBinding绑定、LiveData.oberverve这些方式来更新UI。MVVM相较于MVC、MVP来说,带来了数据持久化、更加解耦、防止内存泄漏等诸多进步,可以用下面的图片来简单表示这种结构:ViewModel和Model之间即有双箭头实线又有双箭头虚线,意思是ViewModel即可以持有Model的引用(虚线),也可以持有Model的实例对象(实线)。
在这里插入图片描述
可以看到同一个ViewModel的LiveData可以被多个Activity/Fragment observe或者DataBinding。

3.5 Demo APP下载

  MVVM架构推荐使用Kotlin编写,更简洁,和JetPack组件结合得更好。这里Koltin、Java的实现都一起给出。

3.5.1 Kotlin Demo APP下载

  
点击下载Kotlin Demo APP ,GitHub链接:https://github.com/xuhao120833/MVVM_Kotlin
  

3.5.2 Java Demo APP下载

  
点击下载Java Demo APP ,GitHub链接:https://github.com/xuhao120833/MVVM_Java
  

四、总结

  总的看起来,MVVM比MVC、MVP先进得多,但是也较为复杂,容易出错。如果你写的APP对性能、内存这些要求没那么高,完全可以不用MVVM。最后还是一句话:没有最好的架构,只有最适合的架构。熟练掌握一种,就能工作了。但是对所有架构的持续学习是必须要做的。

注:还没来得及校对,如果有错别字和表达不清的地方还请见谅,后续会陆续改正的。

相关文章:

Android MVC、MVP、MVVM三种架构的介绍和使用。

写在前面&#xff1a;现在随便出去面试Android APP相关的工作&#xff0c;面试官基本上都会提问APP架构相关的问题&#xff0c;用Java、kotlin写APP的话&#xff0c;其实就三种架构MVC、MVP、MVVM&#xff0c;MVC和MVP高度相似&#xff0c;区别不大&#xff0c;MVVM则不同&…...

AI视频领域的DeepSeek—阿里万相2.1图生视频

让我们一同深入探索万相 2.1 &#xff0c;本文不仅介绍其文生图和文生视频的使用秘籍&#xff0c;还将手把手教你如何利用它实现图生视频。 如下为生成的视频效果&#xff08;我录制的GIF动图&#xff09; 如下为输入的图片 目录 1.阿里巴巴全面开源旗下视频生成模型万相2.1模…...

IDEA 2024.1.7 Java EE 无框架配置servlet

1、创建一个目录&#xff08;文件夹&#xff09;lib来放置我们的库 2、将tomcat目录下的lib文件夹中的servlet-api.jar文件复制到刚创建的lib文件夹下。 3、把刚才复制到lib下的servlet-api.jar添加为库 4、在src下新建一个package&#xff1a;com.demo&#xff0c;然后创…...

STM32---FreeRTOS中断管理试验

一、实验 实验目的&#xff1a;学会使用FreeRTOS的中断管理 创建两个定时器&#xff0c;一个优先级为4&#xff0c;另一个优先级为6&#xff1b;注意&#xff1a;系统所管理的优先级范围 &#xff1a;5~15 现象&#xff1a;两个定时器每1s&#xff0c;打印一段字符串&#x…...

深色系B端系统界面,在何种场景下更加适合?

在数字化办公日益普及的当下&#xff0c;B 端系统已成为企业运营管理不可或缺的工具。B 端系统界面设计的优劣&#xff0c;直接影响着用户体验和工作效率。界面不仅仅是人与系统交互的媒介&#xff0c;更是企业业务流程的可视化呈现。随着设计理念和技术的不断发展&#xff0c;…...

如何使用 Python+Flask+win32print 实现简易网络打印服务1

Python 实现网络打印机&#xff1a;Flask win32print 在工作场景中&#xff0c;我们可能需要一个简单的网页接口&#xff0c;供他人上传文档并自动打印到指定打印机。 本文将演示如何使用 Python Flask win32print 库来实现这一需求。 代码详见&#xff1a;https://github.…...

深度学习DNN实战

导包&#xff1a; import matplotlib as mpl import matplotlib.pyplot as plt %matplotlib inline import numpy as np import sklearn import pandas as pd import os import sys import time from tqdm.auto import tqdm import torch import torch.nn as nn import torch…...

课程3. 分批训练与数据规范、标准化

课程3. 分批训练与数据规范、标准化 理论神经网络的梯度优化反向传播算法 批量训练网络输入的规范化BatchNorm 验证样本实践加载数据集网络构建训练神经网络 课程计划&#xff1a; 1.理论&#xff1a; 批量训练&#xff1b; 输入数据的规范化&#xff1b; 批量标准化&#xff…...

《机器学习数学基础》补充资料:过渡矩阵和坐标变换推导

尽管《机器学习数学基础》这本书&#xff0c;耗费了比较长的时间和精力&#xff0c;怎奈学识有限&#xff0c;错误难免。因此&#xff0c;除了在专门的网页&#xff08; 勘误和修订 &#xff09;中发布勘误和修订内容之外&#xff0c;对于重大错误&#xff0c;我还会以专题的形…...

linux指令学习--sudo apt-get install vim

1. 命令分解 部分含义sudo以管理员权限运行命令&#xff08;需要输入用户密码&#xff09;。apt-getUbuntu 的包管理工具&#xff0c;用于安装、更新、卸载软件包。installapt-get 的子命令&#xff0c;表示安装软件包。vim要安装的软件包名称&#xff08;Vim 文本编辑器&…...

类和对象—多态—案例2—制作饮品

案例描述&#xff1a; 制作饮品的大致流程为&#xff1a;煮水-冲泡-倒入杯中-加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作产品基类&#xff0c;提供子类制作咖啡和茶叶 思路解析&#xff1a; 1. 定义抽象基类 - 创建 AbstractDrinking 抽象类&#xff0c;该类…...

嵌入式产品级-超小尺寸游戏机(从0到1 硬件-软件-外壳)

Ultra-small size gaming console。 超小尺寸游戏机-Pico This embedded product is mainly based on miniaturization, followed by his game functions are also very complete, for all kinds of games can be played, and there will be relevant illustrations in the fo…...

计算机毕业设计Python+Django+Vue3微博数据舆情分析平台 微博用户画像系统 微博舆情可视化(源码+ 文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

前端开发10大框架深度解析

摘要 在现代前端开发中&#xff0c;框架的选择对项目的成功至关重要。本文旨在为开发者提供一份全面的前端框架指南&#xff0c;涵盖 React、Vue.js、Angular、Svelte、Ember.js、Preact、Backbone.js、Next.js、Nuxt.js 和 Gatsby。我们将从 简介、优缺点、适用场景 以及 实际…...

Mybatis 的关联映射(一对一,一对多,多对多)

前言 在前面我们已经了解了&#xff0c;mybatis 的基本用法&#xff0c;动态SQL&#xff0c;学会使用mybatis 来操作数据库。但这些主要操作还是针对 单表实现的。在实际的开发中&#xff0c;对数据库的操作&#xff0c;常常涉及多张表。 因此本篇博客的目标&#xff1a;通过my…...

深度解码!清华大学第六弹《AIGC发展研究3.0版》

在Grok3与GPT-4.5相继发布之际&#xff0c;《AIGC发展研究3.0版》的重磅报告——这份长达200页的行业圣经&#xff0c;不仅预测了2025年AI技术爆发点&#xff0c;更将「天人合一」的东方智慧融入AI伦理建构&#xff0c;堪称数字时代的《道德经》。 文档&#xff1a;清华大学第…...

/dev/console文件详解

/dev/console概览 /dev/console 是 Linux 系统中的一个特殊设备文件&#xff0c;通常用于与系统的控制台进行交互。它的作用和特点如下&#xff1a; 1. 作用 init 进程&#xff08;PID 1&#xff09;和某些系统服务在启动时会使用 /dev/console 进行日志输出&#xff0c;以确…...

ProfibusDP主站转ModbusTCP网关如何进行数据互换

ProfibusDP主站转ModbusTCP网关如何进行数据互换 在现代工业自动化领域&#xff0c;通信协议的多样性和复杂性不断增加。Profibus DP作为一种经典的现场总线标准&#xff0c;广泛应用于工业控制网络中&#xff1b;而Modbus TCP作为基于以太网的通信协议&#xff0c;因其简单易…...

springboot3 WebClient

1 介绍 在 Spring 5 之前&#xff0c;如果我们想要调用其他系统提供的 HTTP 服务&#xff0c;通常可以使用 Spring 提供的 RestTemplate 来访问&#xff0c;不过由于 RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端&#xff0c;因此存在一定性能瓶颈。根据 Spring 官…...

牛客周赛 Round 83

A.和猫猫一起起舞&#xff01; 思路&#xff1a;遇到‘U’和‘D’&#xff0c;输出‘R’或者‘L’&#xff1b;遇到‘R’和‘L’&#xff0c;输出‘U’或者‘D’.(这题比较简单) AC代码&#xff1a; void solve() {int n, m, k;char ch;cin >> ch;if (ch U || ch D)…...

硬通货用Deekseek做一个Vue.js组件开发的教程

安装 Node.js 与 Vue CLI‌ npm install -g vue/cli vue create my-vue-project cd my-vue-project npm run serve 通过 Vue CLI 可快速生成项目骨架&#xff0c;默认配置适合新手快速上手 目录结构‌ src/ ├── components/ # 存放组件文件 │ └── …...

Windows权限维持之利用安全描述符隐藏服务后门进行权限维持(八)

我们先打开cs的服务端 然后我们打开客户端 我们点击连接 然后弹出这个界面 然后我们新建一个监听器 然后我们生成一个beacon 然后把这个复制到目标主机 然后我们双击 运行 然后cs这边就上线了 然后我们把进程结束掉 然后我们再把他删除掉 然后我们创建服务 将后门程序注册…...

Ubuntu20.04双系统安装及软件安装(七):Anaconda3

Ubuntu20.04双系统安装及软件安装&#xff08;七&#xff09;&#xff1a;Anaconda3 打开Anaconda官网&#xff0c;在右侧处填写邮箱&#xff08;要真实有效&#xff01;&#xff09;&#xff0c;然后Submit。会出现如图示的Success界面。 进入填写的邮箱&#xff0c;有一封Ana…...

【极光 Orbit•STC8A-8H】02. STC8 单片机工程模板创建

【极光 Orbit•STC8A-8H】02. STC8 单片机工程模板创建 七绝单片机 小小芯片大乾坤&#xff0c; 集成世界在其中。 初学虽感千重难&#xff0c; 实践方知奥妙通。 今天的讲法和过去不同&#xff0c;直接来一个多文件模块化的工程模板创建&#xff0c;万事开头难&#xff0c;…...

Spring Boot WebFlux 中 WebSocket 生命周期解析

Spring Boot WebFlux 中的 WebSocket 提供了一种高效、异步的方式来处理客户端与服务器之间的双向通信。WebSocket 连接的生命周期包括连接建立、消息传输、连接关闭以及资源清理等过程。此外&#xff0c;为了确保 WebSocket 连接的稳定性和可靠性&#xff0c;我们可以加入重试…...

PostgreSQL中的事务隔离

1. 事务隔离的概念 在数据库管理系统中&#xff0c;事务隔离是一项重要的功能&#xff0c;它能确保在并发访问数据库时事务之间能够独立运行&#xff0c;不会相互干扰。数据库系统通常支持不同级别的事务隔离&#xff0c;用来满足不同应用程序之间的需求。 2. 事务隔离的种类…...

基于Rye的Django项目通过Pyinstaller用Github工作流简单打包

前言 Rye的介绍和安装 Ryehttps://rye.astral.sh/Rye 完整使用教程_安装rye-CSDN博客https://blog.csdn.net/zhenndbc/article/details/144544692 正文 项目建立 配置好环境后 新建文件夹 新建文件夹&#xff0c;进入项目 初始化 rye init下载依赖 rye syncpycharm 打…...

ubuntu 20.04 C++ 源码编译 cuda版本 opencv4.5.0

前提条件是安装好了cuda和cudnn 点击下载&#xff1a; opencv_contrib4.5.0 opencv 4.5.0 解压重命名后 进入opencv目录&#xff0c;创建build目录 “CUDA_ARCH_BIN ?” 这里要根据显卡查询一下,我的cuda是11&#xff0c;显卡1650&#xff0c;所以是7.5 查询链接&#xff1a;…...

【VUE】第一期——初使用、基本语法

目录 0 前言 1 准备工作 1.1 创建vue实例 1.2 vue开发者工具 2 插值表达式 2.1 基本用法 3 常用指令 3.1 内容渲染指令 3.1.1 v-text 3.1.2 v-html 3.2 条件渲染指令 3.2.1 v-show 3.2.2 v-if 3.2.3 v-else 和 v-else-if 3.3 事件绑定指令 3.3.1 内联语句 3.3…...

计算光学成像与光学计算概论

计算光学成像所涉及研究的内容非常广泛&#xff0c;虽然计算光学成像的研究内容是发散的&#xff0c;但目的都是一致的&#xff1a;如何让相机记录到客观实物更丰富的信息&#xff0c;延伸并扩展人眼的视觉感知。总的来说&#xff0c;计算光学成像现阶段已经取得了很多令人振奋…...