【设计模式】设计模式概述
😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪
文章目录
- 前言
- 设计模式概述
- 一、设计模式的历史
- 二、理解面向对象
- 三、软件设计的复杂性
- 四、软件设计的目标
- 后记
前言
大家好,我是白晨。不知不觉已经🕊了很久了,白晨在这里给大家道个歉。
本次为大家带来的是全新的专栏——设计模式
,许多同学学习了面向对象的概念,但是面向对象在实际开发中到底好不好用、到底如何使用,这个问题可能很多人都无法回答。同时,在现在的面试中,设计模式被提及的概率越来越高,“你都使用过什么设计模式?”、“在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)。 他们所提出的设计模式主要是基于以下的面向对象设计原则:对接口编程而不是对实现编程,优先使用对象组合而不是继承。
![]()
总体来说,设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
虽然GoF距离现在已有近30年的历史,部分设计模式也因为种种原因被时代所抛弃,但是GoF中的大部分的设计模式依然被广泛的使用,书中所提出的23种经典面向对象,创立了模式在软件设计中的地位。
虽然GoF提出的设计模式被默认为“面向对象的设计模式”,但是这并不意味着“设计模式”就等同于“面向对象的设计模式”。
二、理解面向对象
面向对象编程的三大特性是:封装、继承和多态。这些特性是面向对象编程的基础,它们使得代码更加模块化、可重用和可维护。
封装
:封装是指将数据和基于数据的操作封装在一起,形成一个独立的实体。封装可以隐藏实现细节,使得代码模块化。良好的封装能够减少耦合,类内部的结构可以自由修改,可以对成员进行更精确的控制,隐藏信息,实现细节。
继承
:继承是使用已存在的类的定义作为基础建立新类的技术。通过使用继承我们能够非常方便地复用以前的代码,能够大大提高开发的效率。继承所描述的是“is-a”的关系,如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B。
多态
:多态是指同一操作作用于不同的对象上面时,可以产生不同的解释和不同的执行结果。多态性分为编译时多态性和运行时多态性。编译时多态性又称为静态多态性,主要实现方式是函数重载和运算符重载;运行时多态性又称为动态多态性,主要实现方式是虚函数。
作为一名程序员我们应该懂得从编程底层,从微观角度去理解对象模型,常见的底层思维有:
- 语言构造
- 编译转换
- 内存模型
- 运行时机制
如果说向下的底层思维是一个程序员必修课,那么向上的抽象思维就是就是进化为一个优秀的程序员必备素养,什么是抽象思维呢?
简单地说,就是将我们周围的事物转化成为代码的能力。
举个例子,如果我们要将我们的学校抽象成代码,首先我们需要大致划分出学校中的人员:学生和老师;其次,要描述学校的建筑材料、建筑风格、具体布局;最后,要编写学校中的各种事件。
以上的过程,老师、学生这样的人员可以抽象为对象,学校的建材、建筑也可以抽象为对象,发生事件更不用说,为了方便管理和执行,得有个统一的对象模型将其组织起来。
对象抽象出来以后,要开始考虑如何实现这些对象,是每个对象都写一个对象模型,还是将其共同点提炼,具体实现一个对象时继承这个共同对象。
最后,要考虑代码的可维护性,因为学校总是要装修、扩建的,每年也都有毕业生和新生。如果代码设计不好,可维护性较差,就会出现牵一发而动全身的情况,明明是一点小改动,就要修改大部分代码,所以,选择合适的设计模式
非常重要。
程序员常见的抽象思维有:
- 面向对象
- 组件封装
- 设计模式
- 架构模式
对于程序员来说,底层思维和抽象思维相互依存,共同决定一个程序员的上限。向下,要能深入理解三大面向对象机制:
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
向上,深刻把握面向对象机制所带来的抽象意义,理解如何使用这些机制来表达现实世界,掌握什么是“好的面向对象设计” 。
三、软件设计的复杂性
建筑商从来不会去想给一栋已建好的100层高的楼房底下再新修一个小地下室——这样做花费极大而且注定要失败。然而令人惊奇的是,软件系统的用户在要求作出类似改变时却不会仔细考虑,而且他们认为这只是需要简单编程的事。——Object-Oriented Analysis and Designwith Applications
![]()
为什么软件设计会变得复杂?
根本原因是:
无论是什么原因引起的,但是只要用户有需求,就会有功能上的变化,一旦要满足相应的功能,我们的代码就得跟着变化。
所以,我们应该如何解决由变化引起的复杂性呢?
- 首先,我们应该将大问题进行分解,分解为一个个小问题,也就是我们在编程中经常使用的——分而治之,将复杂问题分解为独立的简单问题。
- 其次,我们应该从更高的维度去观察这个问题,将其这个问题抽象成一类问题,抓住其本质特征,构建一个理想化或者说泛化的模型,通过解决这个泛化模型的问题,我们解决一类问题,这也是一种复用逻辑。
所以,软件设计的复杂性的一般性的解决方法为分解
和抽象
。
下面我们通过一个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;}
};
下面是这段代码的主要执行逻辑:
MainForm
类是主窗体类,继承自Form
类。它有两个私有成员变量p1
和p2
,分别用于存储鼠标按下和抬起时的坐标。它还有两个向量成员变量lineVector
和rectVector
,分别用于存储所有的线段和矩形。- 当鼠标按下时,会触发
OnMouseDown
事件处理函数。该函数会记录鼠标按下时的坐标(即点p1
)。 - 当鼠标抬起时,会触发
OnMouseUp
事件处理函数。该函数会记录鼠标抬起时的坐标(即点p2
),然后根据当前选择的工具(线段或矩形),创建相应的图形并添加到对应的向量中。 - 在鼠标抬起事件处理函数中,还会调用窗体的刷新方法(即
this->Refresh()
),这将触发绘图事件。 OnPaint
是绘图事件处理函数。当窗体刷新时,会遍历所有的线段和矩形,并在窗体上绘制出来。Point
,Line
,Rect
是一些基础的图形类。其中,点类包含x和y两个属性;直线类包含起点和终点两个属性,并有构造函数初始化这两个属性;矩形类包含左上角点、宽度和高度三个属性,并有构造函数初始化这三个属性。
现在要在原本绘制图形的基础上添加一个绘制“圆形”的功能,
- 首先,应该在
Shape.h
中添加一个圆类
:
- 其次,要修改
MainForm.cpp
中的MainForm
类,在其中添加一个存储所有圆形的数组:
- 再者,要修改
MainForm.cpp
中MainForm::OnMouseUp
函数,添加圆形工具的检测,如果选择工具为圆形,添加此圆形到数组:
- 最后,要修改
MainForm.cpp
中的MainForm::OnPaint
函数,添加圆形的绘制逻辑:
由上可见,我们为了添加一个圆形绘制功能,至少要修改四处地方,如果换成更加复杂的逻辑,其中牵扯的类和交互会变得更多,这就是由于变化
所引起的软件设计的复杂性。
所以,我们要来降低软件设计的复杂性,首先,我们想到的是分解
,但是分解面对现在的问题已经没有什么效果了,因为每个对象都已经解决一个小任务了,并且绘制功能也已经正常工作的,现在要面对的是维护和进一步开发的问题。
所以,我们现在需要抽象
,也即重新构筑代码结构,使代码变得可维护性高、可拓展性高。
现在来观察一下原本的代码有什么问题:
- 见下图,
MainForm
依赖了一个经常变化的Shape.h
,或者说MainForm
依赖了图形类的具体实现,这样设计是非常差劲的,一旦在Shape.h
添加新图像类,就必须更改MainForm
中的多个成员,导致MainForm
更改过于频繁,这是我们不想看到的,我们理想中的MainForm
应该是长期可以不用修改,如果我们要增添绘制功能,应该只用在Shape.h
中添加新类即可。

古代的大禹治水告诉了我们一个经验方法:堵不如疏
。对于变化,如果变化遍布多个类之间,那么一旦发生变化,连带的所有类都必须修改,但是,我们可以将变化疏导到个别类中,使得变化只能影响个别类,而不能对多个类造成影响。
观察现在的依赖,MainForm
是依赖于不稳定的Shape
的,如果要让MainForm
保持稳定,那么它必须依赖一个稳定的类。
什么类是稳定的呢?一种是长时间保持不变的类,另一种就是抽象类
。长时间保持稳定的类在我们这个demo中是没有了,但是我们可以给Shape.h
中的具体类抽象出一个父类Shape
,让具体图形类全部继承这个Shape
抽象类。
- 重构后的
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
中的成员对象不能依赖具体类,应该依赖抽象类:

- 其次,
MainForm::OnPaint
不应该依赖于具体实现,应该让图像类自己绘制自己:

-
最后,还有
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();
}
重构后的代码的依赖关系变为:
由上图可得,MainForm
和Line
都依赖于Shape
这个抽象类,这个关系是稳定的,变化被集中到Shape
的具体实现中,如果现在要添加一个绘制图像,只需要在Shape.h
中添加shape
的子类即可(这里假设MainForm::OnMouseUp
中的变化也被消除)。
这里要注意:变化依然存在,我们也不可能完全消除变化,我们能做的只能是将变化集中管理。
我们可以从上面的demo中总结两条原则:
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
MainForm
不应该依赖于变化的具体图形类,而应该依赖抽象的Shape
类,相应的,具体的图像类也应该依赖于抽象的Shape
类。
- 类模块应该是可扩展的,但是不可修改。
发生变化时,我们不应该过多的修改
MainForm
的具体实现细节,如果经常修改其实现细节,就得考虑代码的依赖关系是否出现了问题;同样的,Shape
的结构也应该是不可修改的,但是对于拓展,也即实现其子类应该是被允许的。
这两条原则非常重要,我们将会在下一篇文章中详细讲解设计模式的原则。
四、软件设计的目标
什么是好的软件设计?软件设计的金科玉律:
复用
正如我们前文提到的:可复用是模式的精髓,前人在面对软件设计的常见问题时,抽象出了适用于同类问题的通用模式,后人可以直接复用前人的模式进行设计,减少了试错的成本,加速了软件的开发,这就是可复用的优势。
所以,在这里我们可以给设计模式下个定义:
设计模式是可复用的经典解决方案,用于解决软件设计中的常见问题。
后记
在本文中,我们探讨了设计模式的历史、面向对象编程的基本概念、软件设计的复杂性以及软件设计的目标。设计模式作为一种强大的工具,可以帮助我们更好地应对日益复杂的软件开发挑战。
通过分解和抽象,我们可以降低软件设计的复杂性,提高代码的可维护性和可扩展性。设计模式的应用能够使我们的代码更具稳定性,减少耦合,提高代码的复用性,以更加高效和灵活的方式开发软件。
下一篇中,我们将着重探讨面向对象设计原则
,它是设计模式中最形而上的东西,也是所有设计模式所要遵守的金科玉律。
如果大家喜欢这个系列,还请大家多多支持啦😋!
如果这篇文章有帮到你,还请给我一个大拇指
👍和小星星
⭐️支持一下白晨吧!喜欢白晨【设计模式】系列的话,不如关注
👀白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。
- 参考资料
《C++设计模式》——李建忠
《HeadFirst设计模式(第二版)》
C++多态_drogon c+±CSDN博客
相关文章:

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

第六届“中国法研杯”司法人工智能挑战赛进行中!
第六届“中国法研杯”司法人工智能挑战赛 赛题上新! 第六届“中国法研杯”司法人工智能挑战赛(LAIC2023)目前已发布司法大模型数据和服务集成调度 、证据推理、司法大数据征文比赛、案件要素识别四大任务。本届大赛中,“案件要素…...

关于 passing ‘const xx’ as ‘this’ argument of 的错误
今天在写一个简单的函数时,编译时出现了如下的错误: 这个很简单的函数是这样的: struct bundle_set {uint32_t baseId;uint32_t endId;bool operator< (const bundle_set &a){return baseId < a.baseId;} }; 在网上搜索到都是说什…...

数据结构和算法(13):优先级队列
概述 按照事先约定的优先级,可以始终高效查找并访问优先级最高数据项的数据结构,也统称作优先级队列 优先级队列将操作对象限定于当前的全局极值者。 根据数据对象之间相对优先级对其进行访问的方式,与此前的访问方式有着本质区别…...
面试经典150题——Day15
文章目录 一、题目二、题解 一、题目 135. Candy There are n children standing in a line. Each child is assigned a rating value given in the integer array ratings. You are giving candies to these children subjected to the following requirements: Each chil…...

web APIs——第一天(上)
变量声明的时候建议 const优先,尽量使用const 原因: const语义化更好很多变量我们声明的时候就知道他不会被更改了,那为什么不用const呢?实际开发中也是,比如react框架,基本const如果你有纠结的时候&…...
【Leetcode】215. 数组中的第K个最大元素
一、题目 1、题目描述 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例1: 输入: [3,2,1,5,6,4], k = 2 输出…...

服务器数据恢复-RAID5常见故障的数据恢复方案
raid5阵列常见故障: 1、服务器硬件故障或者RAID阵列卡故障; 2、服务器意外断电导致的磁盘阵列故障; 3、服务器RAID阵列阵列磁盘出现物理故障,如:电路板坏、磁头损坏、盘面划伤、坏扇区、固件坏等; 4、误操作…...

12个VIM编辑器的高级玩法
vim 是一个很好用的编辑器,应用十分广泛。但关于 vim,总有一些你不知道的事情,我们需要持续不断的学习。 我经常使用 vim,也经常在各大社区、论坛看到 vim 专家用户分享经验,今天我们就总结其中常用的一部分ÿ…...
⽜客论坛的笔记
项目描述: 一个基本功能完整的论坛项目。项目主要功能有: 基于邮件激活的注册方式,基于MD5加密与加盐的密码存储方式,登录功能加入了随机验证码的验证,实现登陆状态检查、为游客与已登录用户展示不同界面与功能。支持用户上传头像,…...
JS逆向分析某枝网的HMAC加密、wasm模块加密
这是我2022年学做JS逆向成功的例子,URL:(脱敏处理)aHR0cHM6Ly93d3cuZ2R0di5jbi9hdWRpb0NoYW5uZWxEZXRhaWwvOTE 逆向分析: 1、每次XHR的GET请求携带的headers包括: {"X-ITOUCHTV-Ca-Timestamp":…...

论坛介绍|COSCon'23开源商业(V)
众多开源爱好者翘首期盼的开源盛会:第八届中国开源年会(COSCon23)将于 10月28-29日在四川成都市高新区菁蓉汇举办。本次大会的主题是:“开源:川流不息、山海相映”!各位新老朋友们,欢迎到成都&a…...

Flink学习笔记(三):Flink四种执行图
文章目录 1、Graph 的概念2、Graph 的演变过程2.1、StreamGraph (数据流图)2.2、JobGraph (作业图)2.3、ExecutionGraph (执行图)2.4、Physical Graph (物理图) 1、Graph 的概念 Flink 中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph -&g…...

堆-----数据结构
引言 什么是堆?堆是一种特殊的数据结构(用数组表示的树)。 为什么要使用到堆?比如一场比赛,如果使用擂台赛的方式来决出冠军(实力第一),就很难知道实力第二的队伍是什么了。 但是…...

震撼登场 | 拓世科技集团新品亮相成为2023世界VR产业大会全场焦点
在当今世界,新一轮科技革命和产业变革蓬勃发展,虚拟现实作为这一浪潮中的代表性技术,伴随着5G商用及元宇宙概念的迅速兴起,已经成为推动数字经济发展和产业转型升级的关键技术,深刻地改变着人类的生产和生活方式。 10…...
后端接口的查询方式
在与前端对接过程中一直都会遇到一个问题,就是我们后端接口提供好了,自测也通过了,前端却说接口不通,当我们去排查时却发现大都不是接口不通,很多情况是前端使用的姿势不对,比如接口明明写的参数是放到ULR路…...
Maven首次安装配置
所有版本下载地址 http://archive.apache.org/dist/maven/ 配置环境变量 变量名: MAVEN_HOME 值: D:\apache-maven-3.9.5 Path:%MAVEN_HOME%\bin 是否安装成功 mvn -v 出现版本号就安装成功 配置本地仓库 也就是从服务器上下载的JAR包地址&a…...

使用html2canvas将html转pdf,由于table表的水平和竖直有滚动条导致显示不全(或者有空白)
结果: 业务:将页面右侧的table打印成想要的格式的pdf,首先遇到的问题是table表上下左右都有滚轮而html2canvas相当于屏幕截图,那滚动区域如何显示出来是个问题? gif有点模糊,但是大致功能可以看出 可复制…...

EDID详解
文章目录 字节含义一些概念YCC位 文章目录 字节含义一些概念YCC位 字节含义 EDID通常由128个字节组成,这些字节提供了关于显示器的各种详细信息。以下是EDID中每个字节位表示的一般含义: Header(头部): 字节0: Headerÿ…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
起重机起升机构的安全装置有哪些?
起重机起升机构的安全装置是保障吊装作业安全的关键部件,主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理: 一、超载保护装置(核心安全装置) 1. 起重量限制器 功能:实时监测起升载荷&a…...
Yii2项目自动向GitLab上报Bug
Yii2 项目自动上报Bug 原理 yii2在程序报错时, 会执行指定action, 通过重写ErrorAction, 实现Bug自动提交至GitLab的issue 步骤 配置SiteController中的actions方法 public function actions(){return [error > [class > app\helpers\web\ErrorAction,],];}重写Error…...
java+webstock
maven依赖 <dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.3.5</version></dependency><dependency><groupId>org.apache.tomcat.websocket</groupId&…...