【设计模式】设计模式概述

news2025/3/12 11:54:48

😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

在这里插入图片描述

文章目录

  • 前言
  • 设计模式概述
    • 一、设计模式的历史
    • 二、理解面向对象
    • 三、软件设计的复杂性
    • 四、软件设计的目标
  • 后记

前言


大家好,我是白晨。不知不觉已经🕊了很久了,白晨在这里给大家道个歉。

本次为大家带来的是全新的专栏——设计模式,许多同学学习了面向对象的概念,但是面向对象在实际开发中到底好不好用、到底如何使用,这个问题可能很多人都无法回答。同时,在现在的面试中,设计模式被提及的概率越来越高,“你都使用过什么设计模式?”、“在xxx中都使用了哪些设计模式“,这些问题相信有过面试经历的人都会被问到,但是即使是一个编程的老鸟,做到准确无误的使用设计模式和理解设计模式也是非常困难的。

基于以上两点,白晨想详细介绍每种设计模式的设计思想(重点)、编程实现以及如何使用等,让大家可以准确理解设计模式的思想,力求做到通俗易懂。

本篇为引导篇,在本篇中主要探讨一个问题:什么是设计模式,我们将会从设计模式的历史出发,抽丝剥茧地分析设计模式这个概念。

注:本系列文章不适合初学者,需要至少掌握一门面向对象语言(eg. C++\Java\Golang\C#…),并且本系列文章示例代码以C++为主,Java为辅(因为网络上的博客只要提到设计模式,一般都是Java,而C++的设计模式相对来说较少),本文章所有的源码都有C++和Java两个版本,源码见白晨的Gitee仓库,链接见下:

设计模式: 白晨博客专栏《设计模式》源码


设计模式概述


一、设计模式的历史


设计模式的概念最早起源于建筑领域。哈佛大学的建筑学博士克里斯托弗.亚历山大,是建筑学领域的模式之父。他与其研究团队用了约20年的时间,对住宅和周边环境进行了大量的调查研究,发现人们对舒适住宅和城市环境存在一些共同的认同规律,将它们归纳成253个模式。

所以,我们可以总结出模式的一个非常重要的特性:可复用是模式的精髓

在1994年,由Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》(中文译名:设计模式 - 可复用的面向对象软件元素)的书,该书首次提到了软件开发中设计模式的概念。这四位作者合称GOF(四人帮,全拼 Gang of Four)。 他们所提出的设计模式主要是基于以下的面向对象设计原则:对接口编程而不是对实现编程,优先使用对象组合而不是继承。

Design Patterns - Elements of Reusable Object-Oriented Software

总体来说,设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

虽然GoF距离现在已有近30年的历史,部分设计模式也因为种种原因被时代所抛弃,但是GoF中的大部分的设计模式依然被广泛的使用,书中所提出的23种经典面向对象,创立了模式在软件设计中的地位。

虽然GoF提出的设计模式被默认为“面向对象的设计模式”,但是这并不意味着“设计模式”就等同于“面向对象的设计模式”。


二、理解面向对象


面向对象编程的三大特性是:封装、继承和多态。这些特性是面向对象编程的基础,它们使得代码更加模块化、可重用和可维护。

封装:封装是指将数据和基于数据的操作封装在一起,形成一个独立的实体。封装可以隐藏实现细节,使得代码模块化。良好的封装能够减少耦合,类内部的结构可以自由修改,可以对成员进行更精确的控制,隐藏信息,实现细节。

继承:继承是使用已存在的类的定义作为基础建立新类的技术。通过使用继承我们能够非常方便地复用以前的代码,能够大大提高开发的效率。继承所描述的是“is-a”的关系,如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B。

多态:多态是指同一操作作用于不同的对象上面时,可以产生不同的解释和不同的执行结果。多态性分为编译时多态性和运行时多态性。编译时多态性又称为静态多态性,主要实现方式是函数重载和运算符重载;运行时多态性又称为动态多态性,主要实现方式是虚函数。

作为一名程序员我们应该懂得从编程底层,从微观角度去理解对象模型,常见的底层思维有:

  • 语言构造
  • 编译转换
  • 内存模型
  • 运行时机制

如果说向下的底层思维是一个程序员必修课,那么向上的抽象思维就是就是进化为一个优秀的程序员必备素养,什么是抽象思维呢?

简单地说,就是将我们周围的事物转化成为代码的能力。

举个例子,如果我们要将我们的学校抽象成代码,首先我们需要大致划分出学校中的人员:学生和老师;其次,要描述学校的建筑材料、建筑风格、具体布局;最后,要编写学校中的各种事件。

以上的过程,老师、学生这样的人员可以抽象为对象,学校的建材、建筑也可以抽象为对象,发生事件更不用说,为了方便管理和执行,得有个统一的对象模型将其组织起来。

对象抽象出来以后,要开始考虑如何实现这些对象,是每个对象都写一个对象模型,还是将其共同点提炼,具体实现一个对象时继承这个共同对象。

最后,要考虑代码的可维护性,因为学校总是要装修、扩建的,每年也都有毕业生和新生。如果代码设计不好,可维护性较差,就会出现牵一发而动全身的情况,明明是一点小改动,就要修改大部分代码,所以,选择合适的设计模式非常重要。

程序员常见的抽象思维有:

  • 面向对象
  • 组件封装
  • 设计模式
  • 架构模式

image-20231018230435018

对于程序员来说,底层思维和抽象思维相互依存,共同决定一个程序员的上限。向下,要能深入理解三大面向对象机制:

  • 封装,隐藏内部实现
  • 继承,复用现有代码
  • 多态,改写对象行为

向上,深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计” 。


三、软件设计的复杂性


建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。

——Object-Oriented Analysis and Designwith Applications

50张建筑效果图欣赏(3) - 设计之家

为什么软件设计会变得复杂?

根本原因是:

变化

无论是什么原因引起的,但是只要用户有需求,就会有功能上的变化,一旦要满足相应的功能,我们的代码就得跟着变化。

所以,我们应该如何解决由变化引起的复杂性呢?

  • 首先,我们应该将大问题进行分解,分解为一个个小问题,也就是我们在编程中经常使用的——分而治之,将复杂问题分解为独立的简单问题。
  • 其次,我们应该从更高的维度去观察这个问题,将其这个问题抽象成一类问题,抓住其本质特征,构建一个理想化或者说泛化的模型,通过解决这个泛化模型的问题,我们解决一类问题,这也是一种复用逻辑。

所以,软件设计的复杂性的一般性的解决方法为分解抽象

下面我们通过一个demo来理解上面内容,假设我们要维护一个桌面绘图软件,原代码见下(Java版本原代码点击跳转):

MainForm.cpp

class MainForm : public Form { // MainForm类,继承自Form类
private:
	Point p1; // 鼠标按下时的点
	Point p2; // 鼠标抬起时的点

	vector<Line> lineVector; // 存储所有线段的向量
	vector<Rect> rectVector; // 存储所有矩形的向量

public:
	MainForm(){ // 构造函数
		//...
	}
protected:
	virtual void OnMouseDown(const MouseEventArgs& e); // 鼠标按下事件处理函数
	virtual void OnMouseUp(const MouseEventArgs& e); // 鼠标抬起事件处理函数
	virtual void OnPaint(const PaintEventArgs& e); // 绘图事件处理函数
};

void MainForm::OnMouseDown(const MouseEventArgs& e){ // 鼠标按下事件处理函数实现
	p1.x = e.X; // 记录鼠标按下时的x坐标
	p1.y = e.Y; // 记录鼠标按下时的y坐标

	//...
	Form::OnMouseDown(e); // 调用父类的鼠标按下事件处理函数
}

void MainForm::OnMouseUp(const MouseEventArgs& e){ // 鼠标抬起事件处理函数实现
	p2.x = e.X; // 记录鼠标抬起时的x坐标
	p2.y = e.Y; // 记录鼠标抬起时的y坐标

	if (rdoLine.Checked){ // 如果选择了线段工具
		Line line(p1, p2); // 创建一个新的线段
		lineVector.push_back(line); // 将新线段添加到线段向量中
	}
	else if (rdoRect.Checked){ // 如果选择了矩形工具
		int width = abs(p2.x - p1.x); // 计算矩形宽度
		int height = abs(p2.y - p1.y); // 计算矩形高度
		Rect rect(p1, width, height); // 创建一个新的矩形
		rectVector.push_back(rect); // 将新矩形添加到矩形向量中
	}

	this->Refresh(); // 刷新窗体,触发绘图事件
	Form::OnMouseUp(e);  // 调用父类的鼠标抬起事件处理函数
}

void MainForm::OnPaint(const PaintEventArgs& e){  // 绘图事件处理函数实现

	for (int i = 0; i < lineVector.size(); i++){  // 遍历所有线段并绘制
		e.Graphics.DrawLine(Pens.Red,
			lineVector[i].start.x, 
			lineVector[i].start.y,
			lineVector[i].end.x,
			lineVector[i].end.y);
	}

	for (int i = 0; i < rectVector.size(); i++){  // 遍历所有矩形并绘制
		e.Graphics.DrawRectangle(Pens.Red,
			rectVector[i].leftUp,
			rectVector[i].width,
			rectVector[i].height);
	}

	Form::OnPaint();  // 调用父类的绘图事件处理函数
}

Shape.h

// 点类定义,包含x和y两个属性
class Point{
public:
	int x;
	int y;
};

// 直线类定义,包含起点和终点两个属性,并有构造函数初始化这两个属性
class Line{
public:
	Point start;
    Point end;

    // 直线类构造函数,初始化起点和终点属性
    Line(const Point& start, const Point& end){
        this->start = start;
        this->end = end;
    }
};

// 矩形类定义,包含左上角点、宽度和高度三个属性,并有构造函数初始化这三个属性
class Rect{
public:
	Point leftUp;
    int width;
	int height;

    // 矩形类构造函数,初始化左上角点、宽度和高度属性
    Rect(const Point& leftUp, int width, int height){
        this->leftUp = leftUp;
        this->width = width;
        this->height = height;
    }
};

下面是这段代码的主要执行逻辑:

  1. MainForm类是主窗体类,继承自Form类。它有两个私有成员变量p1p2,分别用于存储鼠标按下和抬起时的坐标。它还有两个向量成员变量lineVectorrectVector,分别用于存储所有的线段和矩形。
  2. 当鼠标按下时,会触发OnMouseDown事件处理函数。该函数会记录鼠标按下时的坐标(即点p1)。
  3. 当鼠标抬起时,会触发OnMouseUp事件处理函数。该函数会记录鼠标抬起时的坐标(即点p2),然后根据当前选择的工具(线段或矩形),创建相应的图形并添加到对应的向量中。
  4. 在鼠标抬起事件处理函数中,还会调用窗体的刷新方法(即this->Refresh()),这将触发绘图事件。
  5. OnPaint是绘图事件处理函数。当窗体刷新时,会遍历所有的线段和矩形,并在窗体上绘制出来。
  6. Point, Line, Rect是一些基础的图形类。其中,点类包含x和y两个属性;直线类包含起点和终点两个属性,并有构造函数初始化这两个属性;矩形类包含左上角点、宽度和高度三个属性,并有构造函数初始化这三个属性。

现在要在原本绘制图形的基础上添加一个绘制“圆形”的功能

  • 首先,应该在Shape.h中添加一个 圆类

image-20231019150313183

  • 其次,要修改MainForm.cpp中的MainForm类,在其中添加一个存储所有圆形的数组:

image-20231019150209158

  • 再者,要修改MainForm.cppMainForm::OnMouseUp函数,添加圆形工具的检测,如果选择工具为圆形,添加此圆形到数组:

image-20231019151209841

  • 最后,要修改MainForm.cpp中的MainForm::OnPaint函数,添加圆形的绘制逻辑:

image-20231019151928622

由上可见,我们为了添加一个圆形绘制功能,至少要修改四处地方,如果换成更加复杂的逻辑,其中牵扯的类和交互会变得更多,这就是由于变化所引起的软件设计的复杂性。

所以,我们要来降低软件设计的复杂性,首先,我们想到的是分解,但是分解面对现在的问题已经没有什么效果了,因为每个对象都已经解决一个小任务了,并且绘制功能也已经正常工作的,现在要面对的是维护和进一步开发的问题。

所以,我们现在需要抽象,也即重新构筑代码结构,使代码变得可维护性高、可拓展性高。

现在来观察一下原本的代码有什么问题:

  • 见下图,MainForm依赖了一个经常变化的Shape.h,或者说MainForm依赖了图形类的具体实现,这样设计是非常差劲的,一旦在Shape.h添加新图像类,就必须更改MainForm中的多个成员,导致MainForm更改过于频繁,这是我们不想看到的,我们理想中的MainForm应该是长期可以不用修改,如果我们要增添绘制功能,应该只用在Shape.h中添加新类即可。
image-20231019172841564

古代的大禹治水告诉了我们一个经验方法:堵不如疏。对于变化,如果变化遍布多个类之间,那么一旦发生变化,连带的所有类都必须修改,但是,我们可以将变化疏导到个别类中,使得变化只能影响个别类,而不能对多个类造成影响。

观察现在的依赖,MainForm是依赖于不稳定的Shape的,如果要让MainForm保持稳定,那么它必须依赖一个稳定的类。

什么类是稳定的呢?一种是长时间保持不变的类,另一种就是抽象类。长时间保持稳定的类在我们这个demo中是没有了,但是我们可以给Shape.h中的具体类抽象出一个父类Shape,让具体图形类全部继承这个Shape抽象类。

image-20231019170445030

  • 重构后的Shape.h
class Shape{
public:
	virtual void Draw(const Graphics& g) = 0;  
	virtual ~Shape() { } 
};

class Point{
public:
	int x;
	int y;
};

class Line: public Shape{
public:
	Point start;
	Point end;

	Line(const Point& start, const Point& end){
		this->start = start;
		this->end = end;
	}

	//实现自己的Draw,负责画自己
	virtual void Draw(const Graphics& g){
		g.DrawLine(Pens.Red, 
			start.x, start.y,end.x, end.y);
	}

};

class Rect: public Shape{
public:
	Point leftUp;
	int width;
	int height;

	Rect(const Point& leftUp, int width, int height){
		this->leftUp = leftUp;
		this->width = width;
		this->height = height;
	}

	//实现自己的Draw,负责画自己
	virtual void Draw(const Graphics& g){
		g.DrawRectangle(Pens.Red,
			leftUp,width,height);
	}

};

//增加
class Circle : public Shape{
public:
	point center; 
    int radius; 

	Circle(const Point& center, int radius){
        this->center = center;
        this->radius = radius;
    }
	//实现自己的Draw,负责画自己
	virtual void Draw(const Graphics& g){
		g.DrawCircle(Pens.Red,
			center,
			radius);
	}
};

再来重构MainForm

  • 首先,将MainForm中的成员对象不能依赖具体类,应该依赖抽象类:
image-20231019171244427
  • 其次,MainForm::OnPaint不应该依赖于具体实现,应该让图像类自己绘制自己:
image-20231019171715164
  • 最后,还有MainForm::OnMouseUp依赖具体实现,如果要消除MainForm::OnMouseUp中的变化,可以使用我们后续文章所讲到的其他设计模式,这里先按下不表。

  • 重构后的MainForm.cpp

class MainForm : public Form {
private:
	Point p1;
	Point p2;
	//针对所有形状
	vector<Shape*> shapeVector;
public:
	MainForm(){
		//...
	}
protected:
	virtual void OnMouseDown(const MouseEventArgs& e);
	virtual void OnMouseUp(const MouseEventArgs& e);
	virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
	p1.x = e.X;
	p1.y = e.Y;

	//...
	Form::OnMouseDown(e);
}

// 通过后续的学习也可以消除其变化,这里先按下不表
void MainForm::OnMouseUp(const MouseEventArgs& e){
	p2.x = e.X;
	p2.y = e.Y;

	if (rdoLine.Checked){
		shapeVector.push_back(new Line(p1,p2));
	}
	else if (rdoRect.Checked){
		int width = abs(p2.x - p1.x);
		int height = abs(p2.y - p1.y);
		shapeVector.push_back(new Rect(p1, width, height));
	}
	//改变
	else if (rdoCircle.checked){
		//...
		int radius = (int)sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2));
		shapeVector.push_back(new Circle(p1, radius));
	}

	//...
	this->Refresh();
	Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

	//针对所有形状
	for (int i = 0; i < shapeVector.size(); i++){

		shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
	}

	//...
	Form::OnPaint();
}

重构后的代码的依赖关系变为:

image-20231019173152407

由上图可得,MainFormLine都依赖于Shape这个抽象类,这个关系是稳定的,变化被集中到Shape的具体实现中,如果现在要添加一个绘制图像,只需要在Shape.h中添加shape的子类即可(这里假设MainForm::OnMouseUp中的变化也被消除)。

这里要注意:变化依然存在,我们也不可能完全消除变化,我们能做的只能是将变化集中管理。

我们可以从上面的demo中总结两条原则:

  • 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。

MainForm不应该依赖于变化的具体图形类,而应该依赖抽象的Shape类,相应的,具体的图像类也应该依赖于抽象的Shape类。

  • 类模块应该是可扩展的,但是不可修改。

发生变化时,我们不应该过多的修改MainForm的具体实现细节,如果经常修改其实现细节,就得考虑代码的依赖关系是否出现了问题;同样的,Shape的结构也应该是不可修改的,但是对于拓展,也即实现其子类应该是被允许的。

这两条原则非常重要,我们将会在下一篇文章中详细讲解设计模式的原则。


四、软件设计的目标


什么是好的软件设计?软件设计的金科玉律:

复用

正如我们前文提到的:可复用是模式的精髓,前人在面对软件设计的常见问题时,抽象出了适用于同类问题的通用模式,后人可以直接复用前人的模式进行设计,减少了试错的成本,加速了软件的开发,这就是可复用的优势。

所以,在这里我们可以给设计模式下个定义:

设计模式是可复用的经典解决方案,用于解决软件设计中的常见问题。



后记


在本文中,我们探讨了设计模式的历史、面向对象编程的基本概念、软件设计的复杂性以及软件设计的目标。设计模式作为一种强大的工具,可以帮助我们更好地应对日益复杂的软件开发挑战。

通过分解和抽象,我们可以降低软件设计的复杂性,提高代码的可维护性和可扩展性。设计模式的应用能够使我们的代码更具稳定性,减少耦合,提高代码的复用性,以更加高效和灵活的方式开发软件。

下一篇中,我们将着重探讨面向对象设计原则,它是设计模式中最形而上的东西,也是所有设计模式所要遵守的金科玉律。

如果大家喜欢这个系列,还请大家多多支持啦😋!

如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ⭐️支持一下白晨吧!喜欢白晨【设计模式】系列的话,不如关注👀白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。


  • 参考资料

《C++设计模式》——李建忠

《HeadFirst设计模式(第二版)》

C++多态_drogon c+±CSDN博客

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

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

相关文章

中文编程工具开发软件实际案例:酒店饭店餐饮点餐管理系统软件编程实例

中文编程工具开发软件实际案例&#xff1a;酒店饭店餐饮点餐管理系统软件编程实例图片如下 软件的安装方法&#xff1a; 软件绿色免安装&#xff0c;压缩包文件解压后&#xff0c;将文件夹复制到电脑的D或E盘里&#xff0c;将软件目录下的红色程序图标按右键发送到桌面快捷方式…

泛微全新低代码平台e-builder在沪发布,超千名与会者共商数字化转型

10月18日下午&#xff0c;泛微低代码平台体验大会在上海顺利举办&#xff0c;大会以“智能、协同、全程数字化”为主题&#xff0c;吸引了上千位政府及企事单位的信息化负责人参与。 活动现场&#xff0c;参会者身临其境地体验了泛微低代码平台&#xff0c;了解了泛微低代码平…

【树莓派c++图像处理起航1】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Qt OPENCV 安装测试&#xff1f;1. 安装qt2.安装opencv 的基础库3. 安装的路就决定了不会一帆风顺3.1.QT 安装出错3.2 运行Qt错误 4. opencv实际路径&#…

汽车辅助系统

目录 一&#xff0c;项目描述 二&#xff0c;项目 功能 三&#xff0c;代码实现 &#xff08;1&#xff09;倒车雷达 (2)AD&#xff08;对 雨滴与光敏电阻传感器进行AD采集&#xff09; &#xff08;3&#xff09;雨刷 &#xff08;4&#xff09;灯光 最后总结&#xf…

干货分享:网页录屏的免费方法!

“网页怎么录屏呀&#xff0c;在浏览器看到一篇文章&#xff0c;觉得挺有价值的&#xff0c;想保存下来&#xff0c;但是不能下载&#xff0c;也不可以复制粘贴&#xff0c;朋友说可以录下来保存&#xff0c;想问问大家&#xff0c;有什么好用免费的网页录屏方法推荐吗&#xf…

Python入门指南

概述&#xff1a; Python是一种简单易学、功能强大的编程语言&#xff0c;广泛应用于数据分析、Web开发、人工智能等领域。本文将为初学者提供一个Python入门指南&#xff0c;从安装到基本语法&#xff0c;帮助您开始编写Python程序。 第一部分&#xff1a;安装Python 1、进入…

单链表经典OJ题 :分割链表

题目&#xff1a; 给你一个链表的头节点 head 和一个特定值 x&#xff0c;请你对链表进行分隔&#xff0c;使得所有小于x 的节点都出现在 大于或等于 x 的节点之前。 你不需要保留 每个分区中各节点的初始相对位置。 图例&#xff1a; 本题的意思&#xff1a; 给定一个数值&am…

C# Onnx Yolov8 Detect 红绿灯检测

效果 lable GreenCircular GreenLeft GreenRight GreenStraight RedCircular RedLeft RedRight RedStraight 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; usi…

从零开始探索C语言(十二)----预处理器、输入输出及文件读写

文章目录 1. 预处理器1.1 预处理器实例1.2 预定义宏1.3 预处理器运算符1.4 参数化的宏 2. 输入和输出2.1 getchar() & putchar() 函数2.2 gets() & puts() 函数 3. 文件读写3.1 打开文件3.2 关闭文件3.3 写入文件3.4 读取文件3.5 二进制 I/O 函数 4. typedef 和 #defin…

YOLOv8改进实战 | 更换主干网络Backbone之轻量化模型Efficientvit

前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…

【java】【MyBatisPlus】【一】快速入门程序

目录 1、创建空项目mybatisProject 2、创建springboot模块 3、删除多余文件 4、修改pom&#xff0c;引入mybatisplus 5、设置application.yml 6、准备实体Emp 7、创建EmpMapper接口 8、测试MybatisQuickstartApplicationTests 前言&#xff1a;学习MyBatisPlus的基本使…

想要隐藏Word文件内容,如何做?四个方法!

Word文件中有些内容想要隐藏&#xff0c;该如何隐藏&#xff1f;今天分享几个方法给大家 方法一&#xff1a; 最简单的方法&#xff0c;将字体颜色与背景颜色设置为一致的&#xff0c;这样就达到了隐藏的效果&#xff0c;选中文字再修改颜色就可以恢复字体 方法二&#xff1a…

MaaS,云厂商在打一场“翻身仗”

今年以来&#xff0c;大模型的热度&#xff0c;让云计算产业为之沸腾。要举出一个最有力的证明&#xff0c;应该是&#xff1a;MaaS&#xff08;Model as Service&#xff09;这种全新模式的出现&#xff0c;一座座“模型工厂”&#xff0c;已经建起来了。 所谓MaaS&#xff0c…

怎样才能去除视频中的背景音乐,保留人声?

做视频剪辑&#xff0c;二次创作的朋友&#xff0c;需要去除视频中的背景音乐&#xff0c;保留人声&#xff1b;或者去除人声&#xff0c;保留背景音乐。如果请身边做视频的朋友帮忙&#xff0c;可有时不能沟通到位&#xff0c;完成后的效果并不是很理想&#xff0c;就很尴尬了…

Leetcode—283.移动零【简单】

2023每日刷题&#xff08;三&#xff09; Leetcode—283.移动零 双指针法yyds&#xff01; void moveZeroes(int* nums, int numsSize){// 双指针法int left 0, right 0;while(right < numsSize) {if(nums[right] ! 0) {int tmp nums[right];nums[right] 0;nums[left]…

【C++】auto 范围for nullptr

目录 一&#xff0c;auto 1&#xff0c;类型别名思考 2&#xff0c;auto 简介 3&#xff0c;auto 的使用细则 1&#xff0c;auto 与指针和引用结合起来使用 2&#xff0c;同一行定义多个变量 3&#xff0c;auto 不能推导的场景 二&#xff0c;基于范围的for循环 1&…

音频信号的频谱分析实例

在前面的文章 信号频谱分析与功率谱密度 中&#xff0c;我们初步探讨了信号频谱分析的概念&#xff0c;并介绍了其数学工具。本篇文章将结合实例&#xff0c;进一步探讨频谱分析在音频信号处理中的应用。 音频信号的频谱分析是一种将时域中的音频信号转换为频域表示的过程&…

华为云CodeArts IDE for Java安装使用教程

本篇内容主要介绍使用华为云CodeArts IDE for Java创建工程、代码补全、运行调试代码、Build构建和测试相关的主要功能。 一、下载安装华为云CodeArts IDE for Java 华为云CodeArts IDE for Java安装要求 至少需要 2 GB RAM &#xff0c;但是推荐8 GB RAM; 至少需要 2.5 GB 硬…

C# Onnx Yolov8 Detect 指纹检测

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;namespace Onnx…