C++设计模式行为模式———中介者模式
文章目录
- 一、引言
- 二、中介者模式
- 三、总结
一、引言
中介者模式是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。
中介者模式可以减少对象之间混乱无序的依赖关系,从而使其耦合松散,限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。
二、中介者模式
典型的计算机是由CPU、内存、硬盘、声卡、显卡、网卡等配件构成,这些配件都插在主板上,主板对于这些计算机配件来讲,就是一个中介者,各个配件之间的数据通信和交互都通过主板进行。设想一下从硬盘上读一个图形文件并显示到屏幕上,涉及计算机配件可能包括硬盘、CPU、内存、显卡等,通过主板,这些配件可以协调工作。但是,如果没有主板,配件之间的数据交换就麻烦了。从理论上来说,每个计算机配件之间都存在着数据交换的可能,这种数据交换看起来非常杂乱,呈现一种网状结构,这种网状结构体现的是一种多对多的关系(一个配件可能要跟其他多个配件进行交互)。
但如果有了主板这个中介者的存在,每个配件只需要与主板打交道,主板可以把某个配件的信息发送给其他配件,配件之间不再需要打交道,那么此时数据的交换就呈现出了一种星状结构,这种结构看起来就简单和清晰。
假设我们打算开发一个游戏的登录功能,一种是“游客登录”,一种是“账号登录”。用户只能使用一种方式进行登录。
选择“游客登录”的好处是不用输入账号和密码就可以直接单击“登录”按钮来登录游戏,坏处是游戏进度只能保存在当前计算机中,如果换一台计算机,游戏进度信息将全部丢失。选择“账号登录”的麻烦之处是需要输人账号和密码后才能单击“登录”按钮来登录游戏,好处是即便换一台计算机,输人同样的账号和密码登录游戏后游戏的进度信息会全部保留。
这个登录界面需要多个UI控件,如单选按钮(登陆方式),编辑框(账号密码),普通按钮等。如果不采用中间模式来编辑代码,代码会是什么样子的呢?
首先先创建一个UI控件的父类:
class CtlParent{
public:CtlParent(string caption):m_caption(caption){}virtual ~CtlParent(){}//当UI控件发生变化时该成员函数会被调用virtual void Changed(map<string, CtlParent*>& tmpuictllist) = 0;//形参所代表的map容器中包含着所有对话框中涉及的UI控件,注意文件头要有#include<map>//设置UI控件启用或禁用virtual void Enable(bool sign) = 0;
protected:string m_caption;//控件上面显示的文字内容,本范例假设每个UI控件上的文字都不同
};
紧接着分别创建普通按钮、单选按钮、编辑控件等。有了Button
、RadioBtn
、EditCtl
子类的定义,接下来就可以实现这些子类的Changed
成员函数了。
class Button : public CtlParent {
public:Button(string caption) :CtlParent(caption) {}//设置按钮的启用或禁用virtual void Enable(bool sign) {if (sign == true)cout << "按钮" << m_caption << "被设置为了启用状态" << endl;elsecout << "按钮" << m_caption << "被设置为了 禁用 / 状态" << endl;//具体实现按钮启用或者禁用的代码略…}//按钮被按下时该成员函数会被调用virtual void Changed(map<string, CtlParent*>& tmpuictllist){if (m_caption == "登录")//按下的是登录按钮cout << "开始游戏登录验证,根据验证结果决定是进人游戏之中还是验证失败给出提示!"<< endl;else if (m_caption == "退出")//按下的是退出按钮,则退出整个游戏cout << "游戏退出,再见!" << endl;}
};//单选按钮相关类
class RadioBtn :public CtlParent {
public:RadioBtn(string caption) :CtlParent(caption) {}//构造函数
public://设置单选按钮的启用或禁用virtual void Enable(bool sign) {//本范例用不到该功能,实现代码略…//设置单选按钮为被选中或者被取消选中,被选中的单选按钮中间有个黑色实心圆点}void Selected(bool sign){if (sign == true)cout << "单选按钮" << m_caption << "被选中" << endl;elsecout << "单选按钮" << m_caption << "被取消选中" << endl;//具体实现单选按钮被选中或者被取消选中的代码略…}//单选按钮被单击时该成员函数会被调用virtual void Changed(map< string, CtlParent*>& tmpuictllist){if (m_caption == "游客登录"){(static_cast<RadioBtn*>(tmpuictllist["游客登录"]))->Selected(1);(static_cast<RadioBtn*>(tmpuictllist["账号登录"]))->Selected(0);tmpuictllist["账号"]->Enable(0);tmpuictllist["密码"]->Enable(0);tmpuictllist["登录"]->Enable(1);}else if (m_caption == "账号登陆"){(static_cast<RadioBtn*>(tmpuictllist["游客登录"]))->Selected(0);(static_cast<RadioBtn*>(tmpuictllist["账号登录"]))->Selected(1);tmpuictllist["账号"]->Enable(1);tmpuictllist["密码"]->Enable(1);tmpuictllist["登录"]->Enable(1);if ((static_cast<EditCtl*>(tmpuictllist["账号"]))->isContentEmpty() || (static_cast<EditCtl*>(tmpuictllist["密码"]))->isContentEmpty()){tmpuictllist["登录"]->Enable(0);}else{tmpuictllist["登录"]->Enable(1);}}}
};
//编辑框相关类
class EditCtl :public CtlParent
{
public:EditCtl(string caption) :CtlParent(caption) {}//构造函数//设置编辑框的启用或禁用void Enable(bool sign){if (sign == true)cout << "编辑框" << m_caption << "被设置为了\"启用\"状态" << endl;elsecout << "编辑框" << m_caption << "被设置为了\"禁用\"状态" << endl;//具体实现编辑框启用或者禁用的代码略…}//是否编辑框中的内容为空bool isContentEmpty() {return m_content.empty();}//编辑框内容发生变化时该成员函数会被调用virtual void Changed(map<string, CtlParent*>& tmpuictllist){if ((static_cast<EditCtl*>(tmpuictllist["账号"]))->isContentEmpty() || (static_cast<EditCtl*>(tmpuictllist["密码"]))->isContentEmpty()){tmpuictllist["登录"]->Enable(0);}else{tmpuictllist["登录"]->Enable(1);}}
private:string m_content = "";//编辑框中的内容
};
在main函数中调用:
// 创建UI控件列表
map<string, CtlParent*> uictllist;// 创建按钮
uictllist["登录"] = new Button("登录");
uictllist["退出"] = new Button("退出");// 创建单选按钮
uictllist["游客登录"] = new RadioBtn("游客登录");
uictllist["账号登录"] = new RadioBtn("账号登录");// 创建编辑框
uictllist["账号"] = new EditCtl("账号");
uictllist["密码"] = new EditCtl("密码");(dynamic_cast<RadioBtn*>(uictllist["游客登录"]))->Selected(true);
//"游客登录"单选按钮设置为选中
(dynamic_cast<RadioBtn*>(uictllist["账号登录"]))->Selected(false);
//"账号登录"单选按钮设置为取消选中
uictllist["账号"]->Enable(false);//"账号"编辑框设置为禁用
uictllist["密码"]->Enable(false);//"密码"编辑框设置为禁用
uictllist["登录"]->Enable(true);//"登录"按钮设置为启用
uictllist["退出"]->Enable(true);//"退出"按钮设置为启用
cout << "-----------------\n";
uictllist["账号登录"]->Changed(uictllist);
// 清理资源
for (auto& pair : uictllist) {delete pair.second;
}
可以看到这样是十分麻烦的。当“账号登录”单选按钮被选中时,会影响到许多其他控件的状态(调用了许多其他控件所属类的成员函数),能看到的是Button
、RadioBtn
、EditCtl
类的Changed
成员函数中还包含各种代码,这属于典型的网状结构一个对象(控件)发生改变时会与许多其他对象进行交互,对象之间彼此耦合,相互纠缠、制约和依赖,大大降低了对象的可复用性。如果新增或删除一个UI控件类,那么与该UI控件交互的其他UI控件类也必须进行修改,而这就违反了开闭原则。
此时就可以使用中介者模式。在中介者模式中会引人一个中介者对象,各个UI控件之间不再需要彼此通信,只与中介者对象进行通信,中介者会把某个UI控件发送来的信息传达给其他UI控件,这样,以往分散在各个UI控件子类中的逻辑处理代码(Button
、RadioBtn
、EditCtl
类的Changed
成员函数中的代码)就可以统一写在中介者类中,中介者负责控制和协调一组对象之间的交互,某个对象不需要知道其他对象的存在,只需要知道中介者的存在并与其打交道即可。
首先,按照中介者设计模式的编写惯例,会先创建一个中介者抽象父类Mediator
,其中会声明一个成员函数(ctlChanged
)接口,UI控件通过调用这个成员函数与中介者对象进行交互。同时,中介者对象为了能够与所有的UI控件交互,也会持有所有UI控件的指针(createCtrl
成员函数)。定义UI控件类的父类CtlParent
,为了与中介者类对象交互,该类中必须有一个指向中介者类对象的指针m_pmediator
,同时应注意其中的Changed
成员函数,控件就是通过该成员函数与中介者对象进行交互的,
Mediator
类和CtlParent
代码如下:
class CtlParent;//中介者父类
class Mediator
{
public:virtual ~Mediator() {}virtual void createCtrl() = 0;virtual void ctlChanged(CtlParent*) = 0;
};
class CtlParent
{
public:CtlParent(Mediator* ptmpm, string caption):m_pmediator(ptmpm), m_caption(caption) {}virtual ~CtlParent() {}//当UI控件发生变化时该成员函数会被调用virtual void Changed(){m_pmediator->ctlChanged(this);}virtual void Enable(bool sign) = 0; //设置UI控件启用或禁用
protected:string m_caption;//控件上面显示的文字内容,本范例假设每个UI控件上的文字都不同Mediator* m_pmediator;
};
接着,分别创建普通按钮、单选按钮、编辑控件相关的类,它们都继承自CtlParent
类,注意,每个类中构造函数的第一个参数都是用于指向中介者类对象的指针:
class Button : public CtlParent {
public:Button(Mediator* ptmpm, string caption):CtlParent(ptmpm, caption) {}//设置按钮的启用或禁用virtual void Enable(bool sign) {if (sign == true)cout << "按钮" << m_caption << "被设置为了启用状态" << endl;elsecout << "按钮" << m_caption << "被设置为了 禁用 / 状态" << endl;//具体实现按钮启用或者禁用的代码略…}};
//编辑框相关类
class EditCtl :public CtlParent
{
public:EditCtl(Mediator* ptmpm, string caption):CtlParent(ptmpm, caption) {}//设置编辑框的启用或禁用void Enable(bool sign){if (sign == true)cout << "编辑框" << m_caption << "被设置为了\"启用\"状态" << endl;elsecout << "编辑框" << m_caption << "被设置为了\"禁用\"状态" << endl;//具体实现编辑框启用或者禁用的代码略…}//是否编辑框中的内容为空bool isContentEmpty() {return m_content.empty();}
private:string m_content = "";//编辑框中的内容
};//单选按钮相关类
class RadioBtn :public CtlParent {
public:RadioBtn(Mediator* ptmpm, string caption):CtlParent(ptmpm, caption) {}public://设置单选按钮的启用或禁用virtual void Enable(bool sign) {//设置单选按钮为被选中或者被取消选中,被选中的单选按钮中间有个黑色实心圆点}void Selected(bool sign){if (sign == true)cout << "单选按钮" << m_caption << "被选中" << endl;elsecout << "单选按钮" << m_caption << "被取消选中" << endl;//具体实现单选按钮被选中或者被取消选中的代码略…}
};
有了上述这些UI控件类,按照中介者设计模式的编写惯例,就可以编写具体的中介者类concrMediator
,该类继承自中介者抽象父类Mediator
,代码如下:
class concrMediator :public Mediator
{
public:~concrMediator(){delete mp_login;delete mp_logout;delete mp_rbtn1;delete mp_rbtn2;delete mp_edtctl1;delete mp_edtctl2;}virtual void createCtrl(){mp_login = new Button(this, "登录");mp_logout = new Button(this, "退出");mp_rbtn1 = new RadioBtn(this, "游客登录");mp_rbtn2 = new RadioBtn(this, "账号登录");mp_edtctl1 = new EditCtl(this, "账号");mp_edtctl2 = new EditCtl(this, "密码");//设置一下默认的UI控件状态mp_rbtn1->Selected(true);//"游客登录"单选按钮设置为选中mp_rbtn2->Selected(false);//"账号登录"单选按钮设置为取消选中mp_edtctl1->Enable(false);//"账号"编辑框设置为禁用mp_edtctl2->Enable(false);//"密码"编辑框设置为禁用mp_login->Enable(true);//"登录"按钮设置为启用mp_logout->Enable(true);//"退出"按钮设置为启用}virtual void ctlChanged(CtlParent* p_ctrl){if (p_ctrl == mp_login) {cout << "开始游戏登录验证,根据验证结果决定是进人游戏之中还是验证失败给出提示! " << endl;}else if (p_ctrl == mp_logout){cout << "游戏退出,再见! " << endl;}else if (p_ctrl == mp_rbtn1){mp_rbtn1->Selected(true);mp_rbtn2->Selected(false);mp_edtctl1->Enable(false);mp_edtctl2->Enable(false);mp_login->Enable(true);}else if (p_ctrl == mp_rbtn2) {mp_rbtn1->Selected(false);mp_rbtn2->Selected(true);mp_edtctl1->Enable(true);mp_edtctl2->Enable(true);mp_login->Enable(!mp_edtctl1->isContentEmpty() && !mp_edtctl2->isContentEmpty());}else if (p_ctrl == mp_edtctl1 || p_ctrl == mp_edtctl2){if (mp_edtctl1->isContentEmpty() || mp_edtctl2->isContentEmpty()){mp_login->Enable(false);}else{mp_login->Enable(true);}}}Button* mp_login = nullptr; //登录按钮Button* mp_logout = nullptr; //退出按钮RadioBtn* mp_rbtn1 = nullptr; //游客登录单选按钮RadioBtn* mp_rbtn2 = nullptr; //账号登录单选按钮EditCtl* mp_edtctl1 = nullptr; //账号编辑框EditCtl* mp_edtctl2 = nullptr; //密码编辑框};
int main() {concrMediator mymedia;mymedia.createCtrl();cout << "当\"账号登录\"单选按钮被按下时:" << endl;mymedia.mp_rbtn2->Changed();//模拟"账号登录"单选按钮被按下,则去通知//中介者,由中介者实现具体的逻辑处理
}
这样实现起来就轻松许多,“账号登录”单选按钮被选中时,会通知中介者处理,所有核心的处理代码都在中介者类中,因为中介者类中有所有UI控件对象的指针,所以中介者可以通过这些指针调用这些UI控件对象的成员函数,以达到正确设置这些UI控件状态的目的,这就相当于通知其他UI控件某个UI控件发生了变动。
中介者模式结构
引人中介者模式的定义:用一个中介对象(中介者)来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互方式。
中介者模式一般包含四种角色:
- 抽象中介者(Mediator):定义一些接口(抽象方法),后面谈到的各个同事对象(各个UI控件)可以调用这些接口来与中介者进行通信。这里指
Mediator
类。 - 具体中介者(ConcreteMediator):继承自抽象中介者类,实现父类中定义的抽象方法,其中保存了各个同事对象的指针用于与同事对象进行通信实现协作行为。这里指
concrMediator
类 - 抽象同事类(Colleague):定义同事类共有的方法(例如
Changed
)和一些需要子类实现的抽象方法(例如Enable
),同时维持了一个指向中介者对象的指针(m_pmediator
),用于与中介者对象通信。这里指CtlParent
类。 - 具体同事类(ConcreteCollegue):继承自抽象同事类,实现父类中定义的抽象方法。以往同事类对象之间的通信在引人中介者模式后,具体的同事类对象就不再需要与其他同事类对象通信(不需要了解其他同事类对象),只需要与中介者通信,中介者会根据实际情况完成与其他同事类对象的通信。这里指
Button
、RadioBtn
、EditCtl
类。
中介者模式一般用于一组以定义良好但复杂的方式进行通信的场合,由终结者负责控制和协调一组对象的交互。
三、总结
中介者将对象之间复杂的沟通和控制方式集中起来处理,将以往对象之间复杂的多对多关系转化为简单的一对多关系。让系统有更好的灵活性和可扩展性。中介者模式将同事对象进行解耦,同事对象之间不再是一种紧耦合关系,它们仅知道中介者,从而减少相互连接的数目。每个同事类对象都可以独立地改变而不会影响到其他同事类对象,更符合开闭原则,进一步增加了对象的复用性。
而且将各种控制逻辑的代码(同事类对象之间的交互)都集中(转移)到了中介者类中实现,这为集中修改提供了便利,而且若将来需要引人新的中介者行为,则可以创建新具体中介者类来实现。代码的集中实现简化了项目的维护,同时类对象之间传递消息的通路变得简单,消除了对象之间错综复杂的关系,但这也会使中介者类的实现变得非常复杂甚至难以维护,尤其是当同事对象之间的交互非常多时,所以,要平衡好同事对象之间交互的复杂度和中介者对象的实现复杂度,再决定是否使用中介者模式,如果中介者的实现太复杂,可能会抵消掉使用该模式在其他方面带来的好处。
也就是说,当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。
应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。
中介者方式实现方式:
-
找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类 (例如更易于维护或更方便复用)。
-
声明中介者接口并描述中介者和各种组件之间所需的交流接口。 在绝大多数情况下, 一个接收组件通知的方法就足够了。
如果你希望在不同情景下复用组件类, 那么该接口将非常重要。 只要组件使用通用接口与其中介者合作, 你就能将该组件与不同实现中的中介者进行连接。
-
实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益。
-
你可以更进一步, 让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂或外观类似。
-
组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。
-
修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。
中介者模式实现了单一职责原则和开闭原则。可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护,而且无需修改实际组件就能增加新的中介者。减轻了多个组件之间的耦合。
外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
- 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
tract-factory)或外观类似。
-
组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。
-
修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。
中介者模式实现了单一职责原则和开闭原则。可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护,而且无需修改实际组件就能增加新的中介者。减轻了多个组件之间的耦合。
外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
- 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
- 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
相关文章:

C++设计模式行为模式———中介者模式
文章目录 一、引言二、中介者模式三、总结 一、引言 中介者模式是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。 中介者模式可以减少对象之间混乱无序的依赖关系&…...

YB2503HV:高效率降压IC,助力电动车、太阳能设备等领域的能源转换
今天我要向大家介绍一款引人注目的产品—— YB2503HV 100V 3A SOP8内置MOS 高效率降压IC。这款单片集成芯片具备可设定输出电流的开关型降压恒压驱动器功能,可广泛应用于电动车、太阳能设备、电子电池充电等领域。让我们一起来看看它的特点和应用吧! 首先…...

如何使用Jest测试你的React组件
在本文中,我们将了解如何使用Jest(Facebook 维护的一个测试框架)来测试我们的React组件。我们将首先了解如何在纯 JavaScript 函数上使用 Jest,然后再了解它提供的一些开箱即用的功能,这些功能专门用于使测试 React 应…...

微网能量管理研究
微网能量管理研究的重点 微网系统的建模 建立分布式能源单元模型以及微网系统的整体运行、协调控制和优化配置等方面的模型 分布式电源控制策略 微网内分布式电源及储能系统运行依赖于电力电子接口技术,需要相应的充放电控制策略 再生能源发电预测 准确预测太阳能…...

Java基础面试题02:简述什么是值传递和引用传递?
面试题:简述什么是值传递和引用传递? 什么是值传递? 值传递(pass by value)是指在调用函数时,把实际参数的值复制一份传递给函数。换句话说,函数内部对参数的任何修改,都不会影响到…...

【STL】10.set与map的模拟实现
一、源码及框架分析 SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等及个头文件中。 map和set的实现结构框架核心部分截取出来如下: // set #ifndef __SGI_STL_INTERNAL_TREE_H #include <stl_tree.h> #endif …...

Playwright(Java版) - 8: Playwright 元素交互的高级应用
在自动化测试中,处理复杂的页面交互是常见的需求。例如,应对动态加载的元素、处理弹窗与对话框、模拟拖放操作,甚至在绘图板上进行绘图操作。 1 动态元素与弹窗处理 1.1 动态元素的加载与等待 动态页面可能会导致元素在操作时尚未完全加载&…...

播放器开发之ffmpeg 硬件解码方案
硬件编解码的概念 硬件编解码是⾮CPU通过烧写运⾏视频加速功能对⾼清视频流进⾏编解码,其中⾮CPU可包括GPU、FPGA或者 ASIC等独⽴硬件模块,把CPU⾼使⽤率的视频解码⼯作从CPU⾥分离出来,降低CPU的使⽤负荷,使得平台能 ⾼效且流畅…...

n、nvm、nrm、pnpm、yarn各种指令大全
n mac的版本管理工具(可能与nvm冲突) 安装 # 使用 npm / yarn npm i -g n yarn global add n # 使用 brew brew install n环境变量 export PATH"/usr/local/n/versions/node:$PATH"命令详解 版本查看 # 查看 n 版本 n --version/-V # 查…...

数据库管理-根据日期字段进行数据筛选更新数据
项目场景 数据插入、更新、查询 数据库中一张审计表格用来记录数据的操作包括数据的id,数据名称sjmc,数据状态sjzt,数据创建时间createtime,数据更新时间updatetime。 具体需求如下: 根据数据名称更新sjzt和update…...

03. 运算符
一、运算符与表达式 运算符 就是对字面量或者变量进行操作的符号;表达式 是指用运算符把字面量或者变量连接起来,符合 Python 语法的式子。不同运算符连接的表达式体现的是不同类型的表达式;Python 中的运算符主要包括 算术运算符、赋值运算符…...

【最优清零方案——贪心+滑动窗口+线段树】
题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1e6 10; int a[N]; struct node {int l, r;int m, p, lazy; } tr[4 * N]; void pushup(node &u, node &l, node &r) {if (l.m r.m){u.m l.m;u.p max(l.p, r.…...

一个点绕任意点旋转后的点的坐标
在平面坐标上,任意点P(x1,y1),绕一个坐标点Q(x2,y2)逆时针旋转θ角度后,新的坐标设为(x, y)的计算公式: x (x1 - x2)*cos(θ) - (y1 - y2)*sin(θ) x2 ; y (x1 - x2)*sin(θ) (y1 - y2)*cos(θ) y2 ; 另一个场景应用,坐标轴绕…...

大数据面试题每日练习--HDFS是如何工作的?
HDFS(Hadoop Distributed File System)是一个分布式文件系统,设计用于存储非常大的文件。它的主要工作原理如下: NameNode:管理文件系统的命名空间,维护文件目录树和文件元数据信息。NameNode记录每个文件…...

Python的3D可视化库 - vedo (2)visual子模块 基本可视化行为
文章目录 1. visual模块的继承关系2. 基类CommonVisual的方法2.1 获取对象信息2.1.1 对象本身信息2.1.2 对象的查找表2.1.3 对象标量范围2.1.4 对象缩略图 2.2 呈现对象2.2.1 在窗口显示1.2.2 对象可见性 2.2.3 对象颜色2.2.4 对象透明度 2.3 添加标度条2.3.1 2D标度条2.3.2 3D…...

Java AIO(NIO.2)
Java AIO(Asynchronous I/O,异步I/O),也被称为NIO.2,是Java平台提供的一种处理异步输入/输出操作的机制。作为Java NIO(New I/O)的扩展,AIO引入了一些新的API和特性,旨在…...

Flink 常用问题及常用配置(有用)
一、Flink 常用问题及常用配置 参数 示例 说明 execution.checkpointing.interval 3min Checkpoint 触发间隔 state.backend rocksdb / filesystem 用于设置statebackend类型, 默认会以内存为statebackend(无法支持大状态) taskmanager.memory.jvm-overhead.max 204…...

RocketMQ: 消息过滤,通信组件,服务发现
消息过滤 1 ) 简单消息过滤 /*** 订阅指定topic下tags分别等于 TagA 或 TagC 或 TagD */consumer.subscribe("TopicTest1", "TagA || TagC || TagD");如以上代码所示,简单消息过滤通过指定多个 Tag 来过滤消息,过滤的动作在服务器进…...

linux ubuntu的脚本知
目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大,用起来也…...

HTTP有哪些风险?是怎么解决的?
一、风险 HTTP是通过明文传输的,存在窃听风险、篡改风险以及冒充风险。 二、如何解决 HTTPS在HTTP的下层加了一个SSL/TLS层,保证了安全,通过混合加密解决窃听风险、数字签名解决篡改风险、数字证书解决冒充风险。 (1࿰…...

3.12MayBeSomeLinearAlgebra
X是M*(D1),XT为(D1)*M Ω是一行D1列,X乘以欧米噶是M行D1列 行是说样本个数,列是特征数量 如果是小样本,那么可能会出现特征数量大于样本个数 如果MD*DM就是M*M,...

学习日志015--python单链表
创建 class Node:def __init__(self,data):# 数据域self.data data# 链接域self.next Noneclass LinkList:def __init__(self,):# 初始化头节点self.head None# 记录链表的长度self.size 0 增加 #头插def insert_head(self,value):# 创建新节点node Node(value)q self…...

如何在Windows右键新建菜单中添加自定义项
Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\.py] "Python.File"[HKEY_CLASSES_ROOT\.py\ShellNew] "NullFile"""[HKEY_CLASSES_ROOT\Python.File] "FriendlyTypeName""文本.py"[HKEY_CLASSES_ROOT\Python.Fil…...

Spring Boot 3.0废弃了JavaEE,改用了Jakarta EE
Spring Boot 3.0废弃了JavaEE,改用了Jakarta EE 历史背景 javax变成Jakarta的主要原因是因为Java EE项目从Oracle转移到了Eclipse Foundation,并改名为Jakarta EE。 JavaEE是从Java 1.2版本开始推出的Java企业级开发平台,最初的名称是J2EE(J…...

pdf文档动态插入文字水印,45度角,旋转倾斜,位于文档中央,多行水印可插入中文
一行水印 /*** param inputFile 你的PDF文件地址* param outputFile 添加水印后生成PDF存放的地址* param waterMarkName 你的水印* return*/public static boolean waterMark(String inputFile,String outputFile, String waterMarkName){try {PdfReader reader new PdfRead…...

[ 渗透测试面试篇-2 ] 针对大规模资产的攻击思路
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...

深入解析 Web 应用中的 CHIPS(Partitioned Cookie Attribute)
深入解析 Web 应用中的 CHIPS(Partitioned Cookie Attribute) 最新发现flask3.1.0 的版本引入了新的特性:对CHIPS的支持。不少同学对这个可能有点陌生,本文带大家了解一下。 为了在隐私保护和功能需求之间取得平衡,Goo…...

从搭建uni-app+vue3工程开始
技术栈 uni-app、vue3、typescript、vite、sass、uview-plus、pinia 一、项目搭建 1、创建以 typescript 开发的工程 npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project2、安装sass npm install -D sass// 安装sass-loader,注意需要版本10,…...

归并排序与逆序对问题(C语言版)
一、引言 归并排序是一种高效且稳定的排序方法,而逆序对问题是算法领域的一个经典问题,本文教大家如何实现归并排序,以及如何使用归并排序去结果逆序对问题 二、归并排序 归并排序思想 分解:将待排序的数组分成两半,…...

网络爬虫总结与未来方向
通过深入学习和实际操作,网络爬虫技术从基础到进阶得以系统掌握。本节将全面总结关键内容,并结合前沿技术趋势与最新资料,为开发者提供实用性强的深度思考和方案建议。 1. 网络爬虫技术发展趋势 1.1 趋势一:高性能分布式爬虫 随…...