当前位置: 首页 > news >正文

C++设计模式创建型模式———生成器模式

文章目录

  • 一、引言
  • 二、生成器/建造者模式
  • 三、总结

一、引言

上一篇文章我们介绍了工厂模式,工厂模式的主要特点是生成对象。当对象较简单时,可以使用简单工厂模式或工厂模式;而当对象相对复杂时,则可以选择使用抽象工厂模式。

工厂用于生产各种对象,这些对象通常是兄弟类,继承自同一个基类。兄弟子类通过实现基类接口,展现不同的行为,并由工厂函数创建。然而,工厂模式在创建对象时并不关注构造细节,处理复杂对象生成时可能显得力不从心。抽象工厂模式专注于生成一系列相关对象,但在对象构造复杂时,其能力也有限。

相较于工厂模式,生成器模式同样用于对象的生成,但更侧重于构造细节,增加了额外的构建流程,以便处理复杂对象的构建需求。

生成器模式也是一种创建型设计模式, 使我们能够分步骤创建复杂对象。 该模式允许使用相同的创建代码生成不同类型和形式的对象。 也就是说,生成器模式就是为了生成一个复杂的对象。

化繁为简,逐个击破。分步骤创建复杂的对象,并且允许使用相同的代码生成不同类型和形式的对象。

在这里插入图片描述

假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。

假设我们要建一个房子,房子由许多部分组成,如门和墙壁。生成器模式可以将对象构造的代码从产品类中抽取出来,并放在一个名为生成器的独立对象中。

该模式会将对象构造过程划分为一组步骤, 比如 build­Walls创建墙壁和 build­Door创建房门创建房门等。 每次创建对象时, 都需要通过生成器对象执行一系列步骤。 重点在于我们无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 木屋的房门可能需要使用木头制造, 而城堡的房门则必须使用石头制造。

在这种情况下, 可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。

我们通过写出不同生成器以不同方式执行相同的任务。

假设我们需要一个木门+石墙的房子。也需要一个石头门+钢铁的房子。在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡。但是, 只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时, 这样的调用才能返回需要的房屋。

然后我们可以进一步的把用于创建一系列生成器的步骤抽取出来,成为一个单独的主管类。主管类去定义创建步骤的执行顺序。而生成器去提供这些步骤的实现。

但是即使没有主管类,我们的用户也可以直接以特定的顺序去调用创建步骤。但是如果有主管类,主管类可以完全隐藏产品的构造细节。客户端只需要将一个生成器与主管类关联,然后使用主管类去构造产品,就能从生成器处获得构造结果了。


二、生成器/建造者模式

这里还是以游戏中的怪物类来讲解。怪物同样分为亡灵类怪物、元素类怪物、机械类怪物。

在创建怪物对象的过程中,有一个创建步骤非常烦琐,把怪物模型创建出来用于显示给玩家。策划规定,任何一种怪物都由头部、躯干(包括颈部、尾巴等)、肢体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类怪物的载人流程相同,但不同种类怪物的细节载人差别很大,所以,将LoadTrunkModelLoadHeadModelLoadLimbsModel(构建模型的子步骤)成员函数写为虚函数以方便在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
*/

上述这些代码用于创建怪物对象以显示给玩家看,但怪物的创建比较复杂,严格地说,应该是怪物模型的载入过程比较复杂,需要按顺序分别载入躯干、头部、四肢模型并实现不同部位模型之间的挂接。但是,目前的代码并不能称为生成器模式。通过对过程进一步拆分还可以进一步提高灵活性。

这里将AssembleLoadTrunkModelLoadHeadModelLoadLimbsModel这些与模型载人与挂接步骤相关的成员函数称为构建过程相关函数。

考虑到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_UndeadBuilderM_ElementBuilderM_MechanicBuilder),就会构建出不同的怪物,可以随时调用MonsterDirector类的SetBuilder成员函数为MonsterDirector(指挥者)指定一个新的构建器以创建不同种类的怪物对象。

在这里插入图片描述

我们可以发现生成器模式包含四种角色。

  • 抽象构建器Builder):为创建一个产品对象的各个部件指定抽象接口
    LoadTrunkModelLoadHeadModelLoadLimbsModel),同时,也会指定一个接口(GetResult)用于返回所创建的复杂对象。这里指MonsterBuilder类。
  • 具体构建器Concrete Builder):实现了Builder接口以创建(构造和装配)该产品的各个部件,定义并明确其所创建的复杂对象,有时也可以提供一个方法用于返回创建好的复杂对象。这里指M_UndeadBuilderM_ElementBuilderM_MechanicBuilder类。
  • 产品Product):指的是被构建的复杂对象,其包含多个部件,由具体构建器创建该产品的内部表示并定义它的装配过程。这里指M_UndeadM_ElementM_Mechanic类。
  • 指挥者Director):又称主管类,这里指MonsterDirector类。该类有一个指向抽象构建器的指针(m_pMonsterBuilder),利用该指针可以在Construct成员函数中调用构建器对象中“构建和装配产品部件”的方法来完成复杂对象的构建,只要指定不同的具体构建器,用相同的构建过程就会构建出不同的产品。同时,Construct成员函数还控制复杂对象的构建次序(例如,在Construct成员函数中对LoadTrunkModelLoadHeadModelLoadLimbsModel的调用是有先后次序的)。在使用这段内容时,只需要生成一个具体的构建器对象,并利用该构建器对象创建指挥者对象并调用指挥者类的Construct成员函数,就可以构建一个复杂的对象。

前面已经说过,从MonsterBuilder分拆出MonsterDirector这步不是必需的,不做分拆可以看作生成器模式的一种退化情形,当然,此时客户端就需要直接针对构建器进行编码了。一般的建议是:如果MonsterBuilder类本身非常庞大、非常复杂,则进行分拆,否则可以不进行分拆,总之,复杂的东西就考虑做拆解,简单的东西就考虑做合并。

生成器模式结构

在这里插入图片描述

生成器模式的核心组成部分如下:

  1. 生成器接口(Builder:声明通用的产品构造步骤。
  2. 具体生成器(Concrete Builders:实现生成器接口,提供不同的构造过程,能够生成不遵循同一接口的产品。
  3. 产品(Products:最终生成的对象,不同生成器构造的产品可以不属于同一类层次结构。
  4. 主管(Director:定义构造步骤的调用顺序,用于创建和复用特定的产品配置。
  5. 客户端(Client:将生成器对象与主管类关联,通过主管类调用生成器来构建产品。可以在不同的构建过程中使用不同的生成器。

三、总结

通过上述案例,我们不难看出生成器模式主要用于分布建立一个复杂的对象,其中的构建步骤是一个稳定的算法,而复杂对象各个部分的创建会有不同的变化。

生成器模式的核心要点在于将构建算法和具体的构建算法分离,这样构建算法就可以被重用,通过编写不同的代码又可以很方便地对构建实现进行功能扩展。引入指挥者类后,只要使用不同的生成器,利用相同的构建过程就可以构建出不同的产品。

构建器接口定义的是如何构建各个部件,也就是说,当需要创建具体部件的时候,交给构建器来做。而指挥者有两个作用:

  • 负责通过部件以指定的顺序来构建整个产品(控制了构建的过程)。
  • 指挥者通过提供Construct接口隔离了客户端(指main主函数中的代码)与具体构建过程必须要调用的类的成员函数之间的关联

对于客户端,只需要知道各种具体的构建器以及指挥者的Construct接口即可,并不需要知道如何构建具体的产品。想象一个项目开发小组,如果main中构建产品的代码由普通组员编写,这项工作自然比较轻松,但是,支撑代码编写所运用的设计模式及实现一般是由组长来完成,显然这项工作要复杂得多。

工厂方法模式与生成器模式也有类似之处,但生成器模式侧重于一步步构建一个复杂的产品对象,构建完成后返回所构建的产品,工厂方法模式侧重于多个产品对象(且对象所属的类继承自同一个父类)的构建而无论产品本身是否复杂。

  • 生成器模式可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。

  • 生成不同形式的产品时, 可以复用相同的制造代码。将一个复杂对象的创建过程封装起来。用同一个构建算法可以构建出表现上完全不同的产品,实现产品构建和产品表现(表示)上的分离。建造者模式也正是通过把产品构建过程独立出来,从而才使构建算法可以被复用。这样的程序结构更容易扩展和复用。

  • 单一职责原则。 可以将复杂构造代码从产品的业务逻辑中分离出来。

  • 产品的实现可以随时被替换(将不同的构建器提供给指挥者)。而且向客户端隐藏了产品内部的表现。

但是有如下缺点:

  • 要求所创建的产品有比较多的共同点,创建步骤(组成部分)要大致相同,如果产品很不相同,创建步骤差异极大,则不适合使用建造者模式,这是该模式使用范围受限的地方。
  • 生成器模式涉及很多的类,例如需要组合指挥者和构建器对象,然后才能开始对象的构建工作。

生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。你可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。

相关文章:

C++设计模式创建型模式———生成器模式

文章目录 一、引言二、生成器/建造者模式三、总结 一、引言 上一篇文章我们介绍了工厂模式&#xff0c;工厂模式的主要特点是生成对象。当对象较简单时&#xff0c;可以使用简单工厂模式或工厂模式&#xff1b;而当对象相对复杂时&#xff0c;则可以选择使用抽象工厂模式。 工…...

基于微信小程序的校园失物招领系统的研究与实现(V4.0)

博主介绍&#xff1a;✌stormjun、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…...

DDRNet模型创新实现人像分割

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【BiLSTM模型实现电力数据预测】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实…...

try…catch…finally语句里return语句的执行顺序是怎样的?

第一种情况 try语句块里面有return语句&#xff0c;catch语句块和finally语句块里面没有return语句。 代码如下&#xff1a; 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文件

因为工作需求&#xff0c;要写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 发布者示例&#xff08;publisher.cpp&#xff09;3.2 订阅者示例&#xff08;subscriber.cpp&#xff09; 4. 机制比较5. 架构比较6. Iceoryx vs Iceoryx2参考资料 0. 引言 Iceoryx2 是一个基于 Rust 实现的开…...

构 造 器

我们创建了一个对象&#xff0c;在其中定义了属性&#xff0c;new一个对象&#xff0c;然后设置对应的属性&#xff0c;但是我们可以在new对象的时候&#xff0c;同时传入我们要设置的属性&#xff0c;这个时候就需要构造器。 特点 构造方法是一个特殊的成员方法&#xff0c;…...

草莓叶片病害识别与分类数据集(猫脸码客 第234期)

草莓叶片病害识别与分类数据集 草莓作为一种重要的经济作物&#xff0c;在全球范围内广泛种植。然而&#xff0c;草莓生产过程中常常受到各种病害的困扰&#xff0c;其中叶片病害尤为严重。为了有效识别、检测和分类草莓叶片病害&#xff0c;构建一个高质量的数据集是至关重要…...

微服务设计模式 - 断路器模式 (Circuit Breaker Pattern)

微服务设计模式 - 断路器模式 (Circuit Breaker Pattern) 定义 断路器模式&#xff08;Circuit Breaker Pattern&#xff09;是云计算和微服务架构中的一种保护性设计模式&#xff0c;其目的是避免系统中的调用链出现故障时&#xff0c;导致系统瘫痪。通过断路器模式&#xff…...

HarmonyOS NEXT 应用开发实战(九、知乎日报项目详情页实现详细介绍)

在本篇博文中&#xff0c;我们将探讨如何使用 HarmonyOS Next 框架开发一个知乎日报的详情页&#xff0c;逐步介绍所用到的组件及代码实现。知乎日报是个小巧完整的小项目&#xff0c;这是一个循序渐进的过程&#xff0c;适合初学者和有一定开发经验的工程师参考。 1. 项目背景…...

lvgl 模拟器移植(V9)

1.模拟器代码下载 1.1&#xff1a;通过git 下载 github链接&#xff1a;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 的中药方剂知识图谱可视化系统

前言 历时一周时间&#xff0c;中药大数据R02系统中药开发完毕&#xff0c;该系统通过scrapy工程获取中药数据&#xff0c;使用python pandas预处理数据生成知识图谱和其他相关数据&#xff0c;利用vuespringbootneo4jmysql 开发系统&#xff0c;具体功能请看本文介绍。 简要…...

(自用)机器学习python代码相关笔记

一些自存的机器学习函数和详细方法记录&#xff0c;欢迎指错。 前言&#xff1a;读取数据方法 import pandas as pd import pandas as pddf pd.read_csv(数据集.csv, header0) # header是从哪一行开始读起&#xff0c;一般是0&#xff0c;也可以取infer 一、数据处理&#…...

docker复现pytorch_cyclegan

1、安装docker 配置docker镜像 添加镜像源至docker engine 2、wsl2安装nvidia-docker 要在Ubuntu中安装NVIDIA Docker&#xff0c;需要满足以下条件&#xff1a; 确保主机已安装NVIDIA的CUDA驱动程序&#xff0c;并使用适用于您操作系统的正确版本。 wsl --update在Ubuntu…...

IDEA2024下安装kubernetes插件并配置进行使用

【1】安装插件 其实2024.2.3下默认已经安装了kubernetes插件&#xff0c;如果你发现自己IDEA中没有&#xff0c;在市场里面检索并下载即可。 【2】kubernetes配置 ① 前置工作 首先你要准备一个config文件和一个kubectl.exe 。 config文件类似如下&#xff1a; apiVersi…...

理解原子变量之二:从volatile到内存序-进一步的认识

目录 实例1 实例2 实例3 内存序中两个最重要的概念 补记 结论 实例1 看下面的例子&#xff1a;在vs2013中建立如下工程&#xff1a; #include <thread> #include <iostream> #include <chrono>bool done false;void worker(){std::this_thread::sle…...

DICOM标准:MR图像模块属性详解——磁共振成像(MR)在DICOM中的应用

目录 引言 磁共振成像&#xff08;MR&#xff09; 一、MR图像模块 二、MR图像属性描述 1、图像类型 (Image Type) 2、抽样每个象素 (Sampling per Pixel) 3、光度插值 (Photometric Interpretation) 4、位分配 (Bits Allocated) 结论 引言 数字成像和通信在医学&#xff08…...

Linux内核与用户空间

Linux内核与用户空间是Linux操作系统中的两个重要概念&#xff0c;它们各自承担着不同的功能和职责&#xff0c;并通过特定的机制进行交互。以下是对Linux内核与用户空间的详细解释&#xff1a; 一、Linux内核 定义&#xff1a;Linux内核是Linux操作系统的核心组件&#xff0c…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...