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控件上的文字都不同
};

紧接着分别创建普通按钮、单选按钮、编辑控件等。有了ButtonRadioBtnEditCtl子类的定义,接下来就可以实现这些子类的Changed成员函数了。

class Button : public CtlParent {
public:
	Button(string caption) :CtlParent(caption) {}
	//设置按钮的启用或禁用
	virtual void Enable(bool sign) {
		if (sign == true)
			cout << "按钮" << m_caption << "被设置为了启用状态" << endl;
		else
			cout << "按钮" << 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;
		else
			cout << "单选按钮" << 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;
		else
			cout << "编辑框" << 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;
}

可以看到这样是十分麻烦的。当“账号登录”单选按钮被选中时,会影响到许多其他控件的状态(调用了许多其他控件所属类的成员函数),能看到的是ButtonRadioBtnEditCtl类的Changed成员函数中还包含各种代码,这属于典型的网状结构一个对象(控件)发生改变时会与许多其他对象进行交互,对象之间彼此耦合,相互纠缠、制约和依赖,大大降低了对象的可复用性。如果新增或删除一个UI控件类,那么与该UI控件交互的其他UI控件类也必须进行修改,而这就违反了开闭原则。

此时就可以使用中介者模式。在中介者模式中会引人一个中介者对象,各个UI控件之间不再需要彼此通信,只与中介者对象进行通信,中介者会把某个UI控件发送来的信息传达给其他UI控件,这样,以往分散在各个UI控件子类中的逻辑处理代码(ButtonRadioBtnEditCtl类的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;
		else
			cout << "按钮" << 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;
		else
			cout << "编辑框" << 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;
		else
			cout << "单选按钮" << 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):继承自抽象同事类,实现父类中定义的抽象方法。以往同事类对象之间的通信在引人中介者模式后,具体的同事类对象就不再需要与其他同事类对象通信(不需要了解其他同事类对象),只需要与中介者通信,中介者会根据实际情况完成与其他同事类对象的通信。这里指ButtonRadioBtnEditCtl类。

中介者模式一般用于一组以定义良好但复杂的方式进行通信的场合,由终结者负责控制和协调一组对象的交互。


三、总结

中介者将对象之间复杂的沟通和控制方式集中起来处理,将以往对象之间复杂的多对多关系转化为简单的一对多关系。让系统有更好的灵活性和可扩展性。中介者模式将同事对象进行解耦,同事对象之间不再是一种紧耦合关系,它们仅知道中介者,从而减少相互连接的数目。每个同事类对象都可以独立地改变而不会影响到其他同事类对象,更符合开闭原则,进一步增加了对象的复用性。

而且将各种控制逻辑的代码(同事类对象之间的交互)都集中(转移)到了中介者类中实现,这为集中修改提供了便利,而且若将来需要引人新的中介者行为,则可以创建新具体中介者类来实现。代码的集中实现简化了项目的维护,同时类对象之间传递消息的通路变得简单,消除了对象之间错综复杂的关系,但这也会使中介者类的实现变得非常复杂甚至难以维护,尤其是当同事对象之间的交互非常多时,所以,要平衡好同事对象之间交互的复杂度和中介者对象的实现复杂度,再决定是否使用中介者模式,如果中介者的实现太复杂,可能会抵消掉使用该模式在其他方面带来的好处。

也就是说,当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。

应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。

中介者方式实现方式:

  1. 找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类 (例如更易于维护或更方便复用)。

  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。 在绝大多数情况下, 一个接收组件通知的方法就足够了。

    如果你希望在不同情景下复用组件类, 那么该接口将非常重要。 只要组件使用通用接口与其中介者合作, 你就能将该组件与不同实现中的中介者进行连接。

  3. 实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益。

  4. 你可以更进一步, 让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂或外观类似。

  5. 组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。

  6. 修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。

中介者模式实现了单一职责原则和开闭原则。可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护,而且无需修改实际组件就能增加新的中介者。减轻了多个组件之间的耦合。

外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

  • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    tract-factory)或外观类似。
  1. 组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。

  2. 修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。

中介者模式实现了单一职责原则和开闭原则。可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护,而且无需修改实际组件就能增加新的中介者。减轻了多个组件之间的耦合。

外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

  • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
  • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一篇保姆式centos/ubuntu安装docker

前言&#xff1a; 本章节分别演示centos虚拟机&#xff0c;ubuntu虚拟机进行安装docker。 上一篇介绍&#xff1a;docker一键部署springboot项目 一&#xff1a;centos 1.卸载旧版本 yum remove docker docker-client docker-client-latest docker-common docker-latest doc…

EasyAnimate:基于Transformer架构的高性能长视频生成方法

这里主要是对EasyAnimate的论文阅读记录&#xff0c;感兴趣的话可以参考一下&#xff0c;如果想要直接阅读原英文论文的话地址在这里&#xff0c;如下所示&#xff1a; 摘要 本文介绍了EasyAnimate&#xff0c;一种利用Transformer架构实现高性能视频生成的高级方法。我们将原…

李宏毅机器学习课程知识点摘要(6-13集)

pytorch简单的语法和结构 dataset就是数据集&#xff0c;dataloader就是分装好一堆一堆的 他们都是torch.utils.data里面常用的函数&#xff0c;已经封装好了 下面的步骤是把数据集读进来 这里是读进来之后&#xff0c;进行处理 声音信号&#xff0c;黑白照片&#xff0c;红…

gpt2的学习

现在学习下gpt2模型做摘要&#xff0c;我们都知道gpt2 是纯decoder&#xff0c;做摘要说话的效果较好。 把数据拆分 按照这个进行tokenizer 用这个tokenizer BertTokenizer.from_pretrained(‘bert-base-chinese’) 2w多词汇表 用交叉熵做lossf&#xff0c; 设好一些简单的…

网络安全设备

防火墙 防火墙是管理和控制网络流量的重要工具&#xff0c;防火墙适用于过滤流量的网络设备。防火墙根据一组定义的规则过滤流量。 静态数据包过滤防火墙 静态数据包过滤防火墙通过检查消息头中的数据来过滤流量。通常&#xff0c;规则涉及源、目标和端口号。静态数据包过滤防…

Python爬虫:深入探索1688关键词接口获取之道

在数字化经济的浪潮中&#xff0c;数据的价值愈发凸显&#xff0c;尤其是在电商领域。对于电商平台而言&#xff0c;关键词不仅是搜索流量的入口&#xff0c;也是洞察市场趋势、优化营销策略的重要工具。1688作为中国领先的B2B电商平台&#xff0c;其关键词接口的获取对于商家来…

SpringCloud Gateway转发请求到同一个服务的不同端口

SpringCloud Gateway默认不支持将请求路由到一个服务的多个端口 本文将结合Gateway的处理流程&#xff0c;提供一些解决思路 需求背景 公司有一个IM项目&#xff0c;对外暴露了两个端口8081和8082&#xff0c;8081是springboot启动使用的端口&#xff0c;对外提供一些http接口…

全面监测Exchange邮件服务器的关键指标

在当今高度信息化的社会&#xff0c;Exchange邮件服务器已成为企业日常通信的重要组成部分。为了确保邮件服务器的稳定运行&#xff0c;及时发现潜在问题并采取相应的解决措施显得尤为重要。监控易作为一款专业的监控工具&#xff0c;为Exchange邮件服务器提供了全方位的监测功…

实用功能,觊觎(Edge)浏览器的内置截(长)图功能

Edge浏览器内置截图功能 近年来&#xff0c;Edge浏览器不断更新和完善&#xff0c;也提供了长截图功能。在Edge中&#xff0c;只需点击右上角的“...”&#xff0c;然后选择“网页捕获”->“捕获整页”&#xff0c;即可实现长截图。这一功能的简单易用&#xff0c;使其成为…

IDEA2023版本配置项目全局编码

IDEA默认的项目编码是UTF-8&#xff0c;有时候拿到别人的代码使用的编码是GBK&#xff0c;虽然可以在idea右下角进行修改&#xff0c;但是一个一个的修改太慢了。所以需要去进行该项目的编码全局配置。接下来直接讲步骤&#xff0c;以IDEA2023版本为例。 第一步 File>Sett…

【Spiffo】环境配置:VScode+Windows开发环境

摘要&#xff1a; 在Linux下直接开发有时候不习惯快捷键和操作逻辑&#xff0c;用Windows的话其插件和工具都更齐全、方便&#xff0c;所以配置一个Windows的开发环境能一定程度提升效率。 思路&#xff1a; 自己本地网络内远程连接自己的虚拟机&#xff08;假定用的是虚拟机…

计算机网络 实验六 组网实验

一、实验目的 通过构造不同的网络拓扑结构图并进行验证&#xff0c;理解分组转发、网络通信及路由选择的原理&#xff0c;理解交换机和路由器在子网划分中的不同作用。 二、实验原理 组网实验是指将多个计算机通过网络连接起来&#xff0c;实现数据的共享和通信。 组网需要考虑…

springboot vue工资管理系统源码和答辩PPT论文

人类现已迈入二十一世纪&#xff0c;科学技术日新月异&#xff0c;经济、资讯等各方面都有了非常大的进步&#xff0c;尤其是资讯与网络技术的飞速发展&#xff0c;对政治、经济、军事、文化等各方面都有了极大的影响。 利用电脑网络的这些便利&#xff0c;发展一套工资管理系统…

【PPTist】添加PPT模版

前言&#xff1a;这篇文章来探索一下如何应用其他的PPT模版&#xff0c;给一个下拉菜单&#xff0c;列出几个项目中内置的模版 PPT模版数据 &#xff08;一&#xff09;增加菜单项 首先在下面这个菜单中增加一个“切换模版”的菜单项&#xff0c;点击之后在弹出框中显示所有的…

输入/输出管理 III(磁盘和固态硬盘)

一、磁盘 【总结】&#xff1a; 磁盘&#xff08;Disk&#xff09;是由表面涂有磁性物质的物理盘片&#xff0c;通过一个称为磁头的导体线圈从磁盘存取数据。在读&#xff0f;写操作期间&#xff0c;磁头固定&#xff0c;磁盘在下面高速旋转。如下图所示&#xff1a; 磁盘盘面…

链表算法速成计划

链表算法速成计划 1.准备工作 1.1创建链表节点结构体 struct ListNode {int val;ListNode* next;ListNode() : val(0), next(NULL) {}ListNode(int x) : val(x), next(NULL) {}ListNode(int x, ListNode* next) : val(x), next(next) {} };1.2 在IDE中创建链表代码 ListNod…

iPhone或iPad接收的文件怎么找?怎样删除?

因为iOS系统和iPadOS系统的特殊性&#xff0c;在使用AirDroid传输文件之后&#xff0c;往往存在“找文件”的难题。本篇文章一次性解释清楚。 文件传输到iPhone/iPad之后&#xff0c;怎样才能找到&#xff1f; iPhone/iPad接收到的全部文件都可以在AirDroid右上角的【时钟】按钮…

猎板 HDI 多阶工艺:高密度互连的核心技术

一、猎板HDI引言 随着电子设备不断向小型化、高性能化和多功能化发展&#xff0c;PCB&#xff08;印刷电路板&#xff09;的设计与制造面临着前所未有的挑战。HDI&#xff08;高密度互连&#xff09;技术应运而生&#xff0c;而其中的多阶工艺更是满足了对更高布线密度、更小尺…

VUE 的前置知识

一、JavaScript----导图导出 1. JS 提供的导入导出机制&#xff0c;可以实现按需导入 1.1 在html页面中可以把JS文件通过 <script src"showMessage.js"></script> 全部导入 1.2 通过在JS文件中写export关键字导出通过 <script src"showMessage…

---Arrays类

一 java 1.Arrays类 1.1 toString&#xff08;&#xff09; 1.2 arrays.sort( )-----sort排序 1&#xff09;直接调用sort&#xff08;&#xff09; Arrays.sort() 方法的默认排序顺序是 从小到大&#xff08;升序&#xff09;。 2&#xff09;定制排序【具体使用时 调整正负…