C++设计模式创建型模式———生成器模式
文章目录
- 一、引言
- 二、生成器/建造者模式
- 三、总结
一、引言
上一篇文章我们介绍了工厂模式,工厂模式的主要特点是生成对象。当对象较简单时,可以使用简单工厂模式或工厂模式;而当对象相对复杂时,则可以选择使用抽象工厂模式。
工厂用于生产各种对象,这些对象通常是兄弟类,继承自同一个基类。兄弟子类通过实现基类接口,展现不同的行为,并由工厂函数创建。然而,工厂模式在创建对象时并不关注构造细节,处理复杂对象生成时可能显得力不从心。抽象工厂模式专注于生成一系列相关对象,但在对象构造复杂时,其能力也有限。
相较于工厂模式,生成器模式同样用于对象的生成,但更侧重于构造细节,增加了额外的构建流程,以便处理复杂对象的构建需求。
生成器模式也是一种创建型设计模式, 使我们能够分步骤创建复杂对象。 该模式允许使用相同的创建代码生成不同类型和形式的对象。 也就是说,生成器模式就是为了生成一个复杂的对象。
化繁为简,逐个击破。分步骤创建复杂的对象,并且允许使用相同的代码生成不同类型和形式的对象。

假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。
假设我们要建一个房子,房子由许多部分组成,如门和墙壁。生成器模式可以将对象构造的代码从产品类中抽取出来,并放在一个名为生成器的独立对象中。
该模式会将对象构造过程划分为一组步骤, 比如 buildWalls创建墙壁和 buildDoor创建房门创建房门等。 每次创建对象时, 都需要通过生成器对象执行一系列步骤。 重点在于我们无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。
当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 木屋的房门可能需要使用木头制造, 而城堡的房门则必须使用石头制造。
在这种情况下, 可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。
我们通过写出不同生成器以不同方式执行相同的任务。
假设我们需要一个木门+石墙的房子。也需要一个石头门+钢铁的房子。在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡。但是, 只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时, 这样的调用才能返回需要的房屋。
然后我们可以进一步的把用于创建一系列生成器的步骤抽取出来,成为一个单独的主管类。主管类去定义创建步骤的执行顺序。而生成器去提供这些步骤的实现。
但是即使没有主管类,我们的用户也可以直接以特定的顺序去调用创建步骤。但是如果有主管类,主管类可以完全隐藏产品的构造细节。客户端只需要将一个生成器与主管类关联,然后使用主管类去构造产品,就能从生成器处获得构造结果了。
二、生成器/建造者模式
这里还是以游戏中的怪物类来讲解。怪物同样分为亡灵类怪物、元素类怪物、机械类怪物。
在创建怪物对象的过程中,有一个创建步骤非常烦琐,把怪物模型创建出来用于显示给玩家。策划规定,任何一种怪物都由头部、躯干(包括颈部、尾巴等)、肢体3个部位组成,在制作怪物模型时,头部、躯干、肢体模型分开制作。每个部位模型都会有一些位置和方向信息,用于挂接在其他部位模型上,比如将头部挂接到躯干部,再将肢体挂接到躯干部就可以构成一个完整的怪物模型。当然,一些在水中的怪物可能不包含四肢,那么将肢体挂接到躯干部这个步骤什么都不做即可。
之所以在制作怪物模型时将头部、躯干、肢体模型分开制作,是便于同类型怪物的3个
组成部位进行互换。试想一下,如果针对亡灵类怪物制作了3个头部、3个躯干以及3个肢体,则最多可以组合出27个外观不同的亡灵类怪物,这既节省了游戏制作成本,又节省了游戏运行时对内存的消耗。
程序需要先把怪物模型载入内存并进行装配以保证正确地显示给玩家看。所以程序需
要进行如下编码步骤:
-
将怪物的躯干模型信息读人内存并提取其中的位置和方向信息;
-
将怪物的头部和四肢模型信息读人内存并提取其中的位置和方向信息;
-
将头部和四肢模型以正确的位置和方向挂接(Mount)到躯干部位,从而装配出完整的怪物模型。
我们实现一个Monster父类。
class Monster
{
public:Monster(int life, int magic, int attack):m_life(life), m_magic(magic), m_attack(attack){}// 创建怪物的纯虚函数,具体实现将在子类中进行void Assemble(string strmodelno)//参数:模型编号,形如“1253679201245”等,每种位的组合都有一些特别的含义,这里无须深究{LoadTrunkModel(strmodelno.substr(4, 3));//载人躯干模型,截取某部分字符串以表示躯干模型的编号LoadHeadkModel(strmodelno.substr(7, 3));//载人头部模型并挂接到躯干模型上LoadLimbsModel(strmodelno.substr(10, 3)); //载人四肢模型并挂接到躯干模型上}virtual void LoadTrunkModel(const string& strno) = 0;virtual void LoadHeadkModel(const string& strno) = 0;virtual void LoadLimbsModel(const string& strno) = 0;virtual~Monster() {}
protected: int m_life; int m_magic;int m_attack;
};
在上述代码中做了很多简化,只是大致的实现代码,在Assemble成员函数中实现了载人一个怪物模型的固定流程一分别载入了躯干、头部、四肢模型并将它们装配到一起,游戏中所有怪物的载入都遵循该流程(其中的代码是稳定的,不发生变化。
因为亡灵类怪物、元素类怪物、机械类怪物的外观差别巨大,所以虽然这3类怪物的载人流程相同,但不同种类怪物的细节载人差别很大,所以,将LoadTrunkModel、LoadHeadModel、LoadLimbsModel(构建模型的子步骤)成员函数写为虚函数以方便在Monster的子类中重新实现。
// 亡灵怪物类
class M_Undead : public Monster {
public:M_Undead(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只亡灵类怪物来到了这个世界" << endl;}void LoadTrunkModel(const string& strno) override {cout << "载入亡灵躯干模型:" << strno << endl;}void LoadHeadkModel(const string& strno) override {cout << "载入亡灵头部模型:" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout << "载入亡灵四肢模型:" << strno << endl;}
};
// 元素类怪物
class M_Element : public Monster {
public:M_Element(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只元素类怪物来到了这个世界" << endl;}void LoadTrunkModel(const string& strno) override {cout << "载入元素躯干模型:" << strno << endl;}void LoadHeadkModel(const string& strno) override {cout << "载入元素头部模型:" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout << "载入元素四肢模型:" << strno << endl;}
};// 机械类怪物
class M_Mechanic : public Monster {
public:M_Mechanic(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只机械类怪物来到了这个世界" << endl;}void LoadTrunkModel(const string& strno) override {cout << "载入机械躯干模型:" << strno << endl;}void LoadHeadkModel(const string& strno) override {cout << "载入机械头部模型:" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout << "载入机械四肢模型:" << strno << endl;}
};
这时在main函数中加入如下代码,创建一个怪物对象并对齐进行生成:
unique_ptr<Monster> undead = make_unique<M_Undead>(100, 50, 20);
undead->Assemble("1253679201245"); // 传入模型编号进行组装
/*
我们会看到如下输入结结果:
一只亡灵类怪物来到了这个世界
载入亡灵躯干模型:679
载入亡灵头部模型:201
载入亡灵四肢模型:245
*/
上述这些代码用于创建怪物对象以显示给玩家看,但怪物的创建比较复杂,严格地说,应该是怪物模型的载入过程比较复杂,需要按顺序分别载入躯干、头部、四肢模型并实现不同部位模型之间的挂接。但是,目前的代码并不能称为生成器模式。通过对过程进一步拆分还可以进一步提高灵活性。
这里将Assemble、LoadTrunkModel、LoadHeadModel、LoadLimbsModel这些与模型载人与挂接步骤相关的成员函数称为构建过程相关函数。
考虑到Monster类中要实现的逻辑功能可能较多,如果把构建过程相关函数提取出来(分离)放到一个单独的类中,不但可以减少Monster类中的代码量,还可以增加构建过程相关代码的独立性,日后游戏中任何由头部、躯干、肢体3个部位组成并需要将头部挂接到躯干部,再将肢体挂接到躯干部的生物,都可以通过这个单独的类实现模型的构建。
// 怪物基类
class Monster {
public:virtual ~Monster() {}
};// 亡灵怪物类
class M_Undead : public Monster {
public:M_Undead() {cout << "一只亡灵类怪物来到了这个世界" << endl;}
};// 元素怪物类
class M_Element : public Monster {
public:M_Element() {cout << "一只元素类怪物来到了这个世界" << endl;}
};// 机械怪物类
class M_Mechanic : public Monster {
public:M_Mechanic() {cout << "一只机械类怪物来到了这个世界" << endl;}
};// 怪物构建器基类
class MonsterBuilder {
public:virtual ~MonsterBuilder() {}// 载入不同部分模型的纯虚函数virtual void LoadTrunkModel(const string& strno) = 0;virtual void LoadHeadkModel(const string& strno) = 0;virtual void LoadLimbsModel(const string& strno) = 0;// 组装模型void Assemble(const string& strmodelno) {LoadTrunkModel(strmodelno.substr(4, 3)); // 躯干模型LoadHeadkModel(strmodelno.substr(7, 3)); // 头部模型LoadLimbsModel(strmodelno.substr(10, 3)); // 四肢模型}// 获取构建结果unique_ptr<Monster> GetResult() { return move(m_pMonster); }protected:unique_ptr<Monster> m_pMonster; // 使用智能指针管理怪物对象
};// 亡灵怪物构建器
class UndeadBuilder : public MonsterBuilder {
public:UndeadBuilder() {m_pMonster = make_unique<M_Undead>(); // 创建亡灵怪物}void LoadTrunkModel(const string& strno) override {cout << "载入亡灵类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;具体要做的事情其实是委托给怪物子类来完成,委托指把本该自已实现的功能转给其他类实现}void LoadHeadkModel(const string& strno) override {cout << "载入亡灵类怪物的头部模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout <<"载入亡灵类怪物的四肢模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}
};// 元素怪物构建器
class ElementBuilder : public MonsterBuilder {
public:ElementBuilder() {m_pMonster = make_unique<M_Element>(); // 创建元素怪物}void LoadTrunkModel(const string& strno) override {cout << "载入元素类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;具体要做的事情其实是委托给怪物子类来完成,委托指把本该自已实现的功能转给其他类实现}void LoadHeadkModel(const string& strno) override {cout << "载入元素类怪物的头部模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout << "载入元素类怪物的四肢模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}
};// 机械怪物构建器
class MechanicBuilder : public MonsterBuilder {
public:MechanicBuilder() {m_pMonster = make_unique<M_Mechanic>(); // 创建机械怪物}void LoadTrunkModel(const string& strno) override {cout << "载入机械类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;具体要做的事情其实是委托给怪物子类来完成,委托指把本该自已实现的功能转给其他类实现}void LoadHeadkModel(const string& strno) override {cout << "载入机械类怪物的头部模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}void LoadLimbsModel(const string& strno) override {cout << "载入机械类怪物的四肢模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码......" << strno << endl;}
};
在上述代码中,可以注意到,在MonsterBuilder类中放置了一个指向Monster类的成员变量智能指针m_pMonster,同时引人GetResult成员函数用于返回这个m_pMonster指针,也就是说,当一个复杂的对象通过构建器构建完成后,可以通过GetResult返回。
重点观察MonsterBuilder类中的Assemble成员函数,前面曾经提过,该成员函数中的代码是稳定的,不会发生变化。所以可以继续把Assemble成员函数的功能拆出到一个新类中(这步拆分也不是必需的)。创建新类MonsterDirector(扮演一个指挥者角色),将
MonsterBuilder类中的Assemble成员函数整个迁移到MonsterDirector类中并按照惯例重新命名为Construct,同时,在MonsterDirector类中放置一个指向MonsterBuilder类的成员变量指针m_pMonsterBuilder,同时对Construct成员函数的代码进行调整(注意也增加了返回值)。完整的MonsterDirector类代码如下:
//指挥者类
class MonsterDirector {
public:// 使用 unique_ptr 作为构造函数的参数MonsterDirector(std::unique_ptr<MonsterBuilder> ptmpBuilder): m_pMonsterBuilder(std::move(ptmpBuilder)) {}// 使用构建器创建怪物unique_ptr<Monster> ConstructMonster(const string& modelNumber) {m_pMonsterBuilder->LoadTrunkModel(modelNumber.substr(0, 3)); // 组装躯干m_pMonsterBuilder->LoadHeadkModel(modelNumber.substr(3, 3)); // 组装头部m_pMonsterBuilder->LoadLimbsModel(modelNumber.substr(6, 3)); // 组装四肢return m_pMonsterBuilder->GetResult();}void SetBuilder() {//指定新的生成器}
private:unique_ptr<MonsterBuilder> m_pMonsterBuilder; // 指向生成器类的父类
};
我们可以这样使用:
string modelNumber = "1253679201245"; // 模型编号// 创建指挥者,并传入智能指针
auto undeadBuilder = std::make_unique<UndeadBuilder>();
MonsterDirector director(std::move(undeadBuilder));// 构造怪物
director.ConstructMonster(modelNumber);
引入生成器模型的定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
在上述范例中,MonsterBuilder类是对象的构建,而Monster类是对象的表示,这两个类是相互分离的。构建过程是指MonsterDirector类中的Construct成员函数所代表的怪物模型的载人和装配(挂接)过程,该过程稳定不会发生变化(稳定的算法),所以只要传递给MonsterDirector不同的构建器子类(M_UndeadBuilder、M_ElementBuilder、M_MechanicBuilder),就会构建出不同的怪物,可以随时调用MonsterDirector类的SetBuilder成员函数为MonsterDirector(指挥者)指定一个新的构建器以创建不同种类的怪物对象。

我们可以发现生成器模式包含四种角色。
- 抽象构建器(Builder):为创建一个产品对象的各个部件指定抽象接口
(LoadTrunkModel、LoadHeadModel、LoadLimbsModel),同时,也会指定一个接口(GetResult)用于返回所创建的复杂对象。这里指MonsterBuilder类。 - 具体构建器(Concrete Builder):实现了
Builder接口以创建(构造和装配)该产品的各个部件,定义并明确其所创建的复杂对象,有时也可以提供一个方法用于返回创建好的复杂对象。这里指M_UndeadBuilder、M_ElementBuilder、M_MechanicBuilder类。 - 产品(Product):指的是被构建的复杂对象,其包含多个部件,由具体构建器创建该产品的内部表示并定义它的装配过程。这里指
M_Undead、M_Element、M_Mechanic类。 - 指挥者(Director):又称主管类,这里指
MonsterDirector类。该类有一个指向抽象构建器的指针(m_pMonsterBuilder),利用该指针可以在Construct成员函数中调用构建器对象中“构建和装配产品部件”的方法来完成复杂对象的构建,只要指定不同的具体构建器,用相同的构建过程就会构建出不同的产品。同时,Construct成员函数还控制复杂对象的构建次序(例如,在Construct成员函数中对LoadTrunkModel、LoadHeadModel、LoadLimbsModel的调用是有先后次序的)。在使用这段内容时,只需要生成一个具体的构建器对象,并利用该构建器对象创建指挥者对象并调用指挥者类的Construct成员函数,就可以构建一个复杂的对象。
前面已经说过,从MonsterBuilder分拆出MonsterDirector这步不是必需的,不做分拆可以看作生成器模式的一种退化情形,当然,此时客户端就需要直接针对构建器进行编码了。一般的建议是:如果MonsterBuilder类本身非常庞大、非常复杂,则进行分拆,否则可以不进行分拆,总之,复杂的东西就考虑做拆解,简单的东西就考虑做合并。
生成器模式结构

生成器模式的核心组成部分如下:
- 生成器接口(Builder):声明通用的产品构造步骤。
- 具体生成器(Concrete Builders):实现生成器接口,提供不同的构造过程,能够生成不遵循同一接口的产品。
- 产品(Products):最终生成的对象,不同生成器构造的产品可以不属于同一类层次结构。
- 主管(Director):定义构造步骤的调用顺序,用于创建和复用特定的产品配置。
- 客户端(Client):将生成器对象与主管类关联,通过主管类调用生成器来构建产品。可以在不同的构建过程中使用不同的生成器。
三、总结
通过上述案例,我们不难看出生成器模式主要用于分布建立一个复杂的对象,其中的构建步骤是一个稳定的算法,而复杂对象各个部分的创建会有不同的变化。
生成器模式的核心要点在于将构建算法和具体的构建算法分离,这样构建算法就可以被重用,通过编写不同的代码又可以很方便地对构建实现进行功能扩展。引入指挥者类后,只要使用不同的生成器,利用相同的构建过程就可以构建出不同的产品。
构建器接口定义的是如何构建各个部件,也就是说,当需要创建具体部件的时候,交给构建器来做。而指挥者有两个作用:
- 负责通过部件以指定的顺序来构建整个产品(控制了构建的过程)。
- 指挥者通过提供Construct接口隔离了客户端(指main主函数中的代码)与具体构建过程必须要调用的类的成员函数之间的关联
对于客户端,只需要知道各种具体的构建器以及指挥者的Construct接口即可,并不需要知道如何构建具体的产品。想象一个项目开发小组,如果main中构建产品的代码由普通组员编写,这项工作自然比较轻松,但是,支撑代码编写所运用的设计模式及实现一般是由组长来完成,显然这项工作要复杂得多。
工厂方法模式与生成器模式也有类似之处,但生成器模式侧重于一步步构建一个复杂的产品对象,构建完成后返回所构建的产品,工厂方法模式侧重于多个产品对象(且对象所属的类继承自同一个父类)的构建而无论产品本身是否复杂。
-
生成器模式可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
-
生成不同形式的产品时, 可以复用相同的制造代码。将一个复杂对象的创建过程封装起来。用同一个构建算法可以构建出表现上完全不同的产品,实现产品构建和产品表现(表示)上的分离。建造者模式也正是通过把产品构建过程独立出来,从而才使构建算法可以被复用。这样的程序结构更容易扩展和复用。
-
单一职责原则。 可以将复杂构造代码从产品的业务逻辑中分离出来。
-
产品的实现可以随时被替换(将不同的构建器提供给指挥者)。而且向客户端隐藏了产品内部的表现。
但是有如下缺点:
- 要求所创建的产品有比较多的共同点,创建步骤(组成部分)要大致相同,如果产品很不相同,创建步骤差异极大,则不适合使用建造者模式,这是该模式使用范围受限的地方。
- 生成器模式涉及很多的类,例如需要组合指挥者和构建器对象,然后才能开始对象的构建工作。
生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。你可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。
相关文章:
C++设计模式创建型模式———生成器模式
文章目录 一、引言二、生成器/建造者模式三、总结 一、引言 上一篇文章我们介绍了工厂模式,工厂模式的主要特点是生成对象。当对象较简单时,可以使用简单工厂模式或工厂模式;而当对象相对复杂时,则可以选择使用抽象工厂模式。 工…...
基于微信小程序的校园失物招领系统的研究与实现(V4.0)
博主介绍:✌stormjun、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&…...
DDRNet模型创新实现人像分割
项目源码获取方式见文章末尾! 600多个深度学习项目资料,快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【BiLSTM模型实现电力数据预测】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实…...
try…catch…finally语句里return语句的执行顺序是怎样的?
第一种情况 try语句块里面有return语句,catch语句块和finally语句块里面没有return语句。 代码如下: public class Main {public static void main(String[] args) {System.out.println(test1());}public static int test1() {int i 10;try {System.o…...
AIGC与虚拟现实(VR)的结合与应用前景
公主请阅 引言1. AIGC与VR的基本概念1.1 AIGC简介1.2 VR技术概述 2. AIGC在VR中的应用2.1 生成虚拟环境2.2 自动生成内容2.3 互动体验 3. AIGC与VR结合的应用案例3.1 教育培训3.2 娱乐与游戏3.3 心理治疗3.4 虚拟旅游 4. AIGC与VR结合的挑战4.1 技术限制4.2 用户体验4.3 数据隐…...
如何在visual studio中 生成 并 使用dll和lib文件
因为工作需求,要写lib和dll给别人使用。 使用visual studio2022 以函数 int getmyset() { return 0;} 为例子 首先 点击打开 visual studio 文件->新建->项目 选择windows桌面向导 选择应用程序类型为动态链接库.dll 分别创建MyDLL.h和MyDLL.cpp文件&a…...
「Mac畅玩鸿蒙与硬件15」鸿蒙UI组件篇5 - Slider 和 Progress 组件
Slider 和 Progress 是鸿蒙系统中的常用 UI 组件。Slider 控制数值输入,如音量调节;Progress 显示任务的完成状态,如下载进度。本文通过代码示例展示如何使用这些组件,并涵盖 进度条类型介绍、节流优化、状态同步 和 定时器动态更新。 关键词 Slider 组件Progress 组件节流…...
Iceoryx2:高性能进程间通信框架(中间件)
文章目录 0. 引言1. 主要改进2. Iceoryx2 的架构3. C示例代码3.1 发布者示例(publisher.cpp)3.2 订阅者示例(subscriber.cpp) 4. 机制比较5. 架构比较6. Iceoryx vs Iceoryx2参考资料 0. 引言 Iceoryx2 是一个基于 Rust 实现的开…...
构 造 器
我们创建了一个对象,在其中定义了属性,new一个对象,然后设置对应的属性,但是我们可以在new对象的时候,同时传入我们要设置的属性,这个时候就需要构造器。 特点 构造方法是一个特殊的成员方法,…...
草莓叶片病害识别与分类数据集(猫脸码客 第234期)
草莓叶片病害识别与分类数据集 草莓作为一种重要的经济作物,在全球范围内广泛种植。然而,草莓生产过程中常常受到各种病害的困扰,其中叶片病害尤为严重。为了有效识别、检测和分类草莓叶片病害,构建一个高质量的数据集是至关重要…...
微服务设计模式 - 断路器模式 (Circuit Breaker Pattern)
微服务设计模式 - 断路器模式 (Circuit Breaker Pattern) 定义 断路器模式(Circuit Breaker Pattern)是云计算和微服务架构中的一种保护性设计模式,其目的是避免系统中的调用链出现故障时,导致系统瘫痪。通过断路器模式ÿ…...
HarmonyOS NEXT 应用开发实战(九、知乎日报项目详情页实现详细介绍)
在本篇博文中,我们将探讨如何使用 HarmonyOS Next 框架开发一个知乎日报的详情页,逐步介绍所用到的组件及代码实现。知乎日报是个小巧完整的小项目,这是一个循序渐进的过程,适合初学者和有一定开发经验的工程师参考。 1. 项目背景…...
lvgl 模拟器移植(V9)
1.模拟器代码下载 1.1:通过git 下载 github链接:GitHub - lvgl/lv_port_pc_visual_studio: Visual Studio projects for LVGL embedded graphics library. Recommended on Windows. Linux support with Wayland is work in progress.https://github.com…...
基于vue+neo4j 的中药方剂知识图谱可视化系统
前言 历时一周时间,中药大数据R02系统中药开发完毕,该系统通过scrapy工程获取中药数据,使用python pandas预处理数据生成知识图谱和其他相关数据,利用vuespringbootneo4jmysql 开发系统,具体功能请看本文介绍。 简要…...
(自用)机器学习python代码相关笔记
一些自存的机器学习函数和详细方法记录,欢迎指错。 前言:读取数据方法 import pandas as pd import pandas as pddf pd.read_csv(数据集.csv, header0) # header是从哪一行开始读起,一般是0,也可以取infer 一、数据处理&#…...
docker复现pytorch_cyclegan
1、安装docker 配置docker镜像 添加镜像源至docker engine 2、wsl2安装nvidia-docker 要在Ubuntu中安装NVIDIA Docker,需要满足以下条件: 确保主机已安装NVIDIA的CUDA驱动程序,并使用适用于您操作系统的正确版本。 wsl --update在Ubuntu…...
IDEA2024下安装kubernetes插件并配置进行使用
【1】安装插件 其实2024.2.3下默认已经安装了kubernetes插件,如果你发现自己IDEA中没有,在市场里面检索并下载即可。 【2】kubernetes配置 ① 前置工作 首先你要准备一个config文件和一个kubectl.exe 。 config文件类似如下: apiVersi…...
理解原子变量之二:从volatile到内存序-进一步的认识
目录 实例1 实例2 实例3 内存序中两个最重要的概念 补记 结论 实例1 看下面的例子:在vs2013中建立如下工程: #include <thread> #include <iostream> #include <chrono>bool done false;void worker(){std::this_thread::sle…...
DICOM标准:MR图像模块属性详解——磁共振成像(MR)在DICOM中的应用
目录 引言 磁共振成像(MR) 一、MR图像模块 二、MR图像属性描述 1、图像类型 (Image Type) 2、抽样每个象素 (Sampling per Pixel) 3、光度插值 (Photometric Interpretation) 4、位分配 (Bits Allocated) 结论 引言 数字成像和通信在医学(…...
Linux内核与用户空间
Linux内核与用户空间是Linux操作系统中的两个重要概念,它们各自承担着不同的功能和职责,并通过特定的机制进行交互。以下是对Linux内核与用户空间的详细解释: 一、Linux内核 定义:Linux内核是Linux操作系统的核心组件,…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
