C++ 设计模式——备忘录模式

news2024/11/15 17:25:48

C++ 设计模式——备忘录模式

    • C++ 设计模式——备忘录模式
      • 1. 主要组成成分
      • 2. 逐步构建备忘录模式
        • 步骤1: 创建备忘录
        • 步骤2: 实现原发器
        • 步骤3: 创建管理者(负责人)类
        • 步骤4: 客户端使用
      • 3. 备忘录模式 UML 图
        • UML 图解析
      • 4. 备忘录模式的优点
      • 5. 备忘录模式的缺点
      • 6. 备忘录模式适用场景
      • 总结
      • 完整代码

C++ 设计模式——备忘录模式

备忘录(Memento)模式也称为快照(Snapshot)模式,是一种行为型模式,主要用于防止数据丢失。它通过对对象的状态进行备份,以便在未来需要时可以恢复这些数据。换句话说,该模式能够将某个时间点的对象内部状态保存下来,并在必要时根据保存的内容将该对象恢复到当时的状态。备忘录模式的结构比较简单,使用频率相对较低,但在特定场景下非常有用。

1. 主要组成成分

  1. 原发器(Originator): 负责创建备忘录对象以及根据备忘录恢复自身状态的类。
  2. 备忘录(Memento): 用于存储原发器的内部状态的类。
  3. 负责人/管理员(Caretaker): 负责管理备忘录的类,控制备忘录的存取,但不修改备忘录的内容。

2. 逐步构建备忘录模式

该示例代码模拟了一个游戏中的玩家角色(Fighter),它能够保存和恢复其状态(如生命值、魔法值和攻击力)。以下是每个步骤的详细说明:

步骤1: 创建备忘录

创建备忘录类,该类将保存原发器的状态。在这一步,定义一个名为 FighterMemento 的类。这个类的主要作用是存储玩家角色的状态信息。具体来说,它保存了玩家的生命值、魔法值和攻击力。通过使用私有构造函数,该类确保只能通过 Fighter 类创建备忘录,从而保护内部状态不被随意访问。

//玩家主角相关的备忘录类
class FighterMemento
{
private:
    //构造函数,用private修饰以防止在外部被随意创建
    FighterMemento(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

private:
    //提供一些供Fighter类来访问的接口,用private修饰防止被任意类访问
    friend class Fighter; //友元类Fighter可以访问本类的私有成员函数
    int getLife() const { return m_life; }
    void setLife(int life) { m_life = life; }
    int getMagic() const { return m_magic; }
    void setMagic(int magic) { m_magic = magic; }
    int getAttack() const { return m_attack; }
    void setAttack(int attack) { m_attack = attack; }
private:
    //玩家主角类中要保存起来的数据,就放到这里来
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
};

步骤2: 实现原发器

定义原发器类 Fighter,它负责创建备忘录并能从备忘录中恢复状态。在此步骤中,定义了 Fighter 类,它代表游戏中的玩家角色。此类包含了角色的基本属性(生命值、魔法值和攻击力),并提供了方法来创建备忘录和从备忘录中恢复状态。通过 createMomento() 方法,当前状态将被保存到备忘录中,而 restoreMomento() 方法则用于从备忘录恢复状态。

//玩家主角类
class Fighter
{
public:
    //构造函数
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

public:
    //将玩家数据写入备忘录(创建备忘录,并在其中存储了当前状态)
    FighterMemento* createMomento()
    {
        return new FighterMemento(m_life, m_magic, m_attack);
    }
    //从备忘录中恢复玩家数据
    void restoreMomento(FighterMemento* pfm)
    {
        m_life = pfm->getLife();
        m_magic = pfm->getMagic();
        m_attack = pfm->getAttack();
    }
    //为测试目的引入的接口,设置玩家的生命值为0(玩家死亡)
    void setToDead()
    {
        m_life = 0;
    }
    //用于输出一些信息
    void displayInfo()
    {
        cout << "玩家主角当前的生命值、魔法值、攻击力分别为:" << m_life << "," << m_magic << "," << m_attack << endl;
    }

private:
    //角色属性
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
    //......其他数据略
};
步骤3: 创建管理者(负责人)类

定义管理者类 FCareTaker,用于管理备忘录。在这一步,定义了 FCareTakerFCareTaker2 类。它们负责管理备忘录对象。FCareTaker 类能够保存单个备忘录,而 FCareTaker2 类支持多个备忘录的管理。在游戏中,这允许存储多个角色状态快照,以便在需要时进行恢复。

//管理者(负责人)类
class FCareTaker
{
public:
    //构造函数
    FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

    //获取指向备忘录对象的指针
    FighterMemento* getMemento()
    {
        return m_pfm;
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfm = ptmpfm;
    }
private:
    FighterMemento* m_pfm; //指向备忘录对象的指针
};

//-----------------
//支持多个快照的负责人(管理者)类
class FCareTaker2
{
public:
    //析构函数用于释放资源
    ~FCareTaker2()
    {
        for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
        {
            delete (*iter);
        } //end for
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfmContainer.push_back(ptmpfm);
    }
    //获取指向备忘录对象的指针
    FighterMemento* getMemento(int index)
    {
        auto iter = m_pfmContainer.begin();
        for (int i = 0; i <= index; ++i)
        {
            if (i == index)
                return (*iter);
            else
                ++iter;
        } //end for
        return nullptr;
    }
private:
    //存储备忘录对象指针的容器
    vector<FighterMemento*> m_pfmContainer;  //#include <vector>
};
步骤4: 客户端使用

在客户端代码中使用备忘录模式。在客户端代码中,实例化 FighterFCareTaker2 对象,并模拟角色的状态变化。通过调用 createMomento() 方法创建备忘录,然后更改角色状态并再次创建备忘录。最后,通过 restoreMomento() 方法从备忘录恢复角色状态,并输出角色的当前状态。

int main()
{

    Fighter* p_fighter = new Fighter(800, 200, 300);
    //(1)显示玩家主角在与BOSS战斗之前的信息
    p_fighter->displayInfo();

    //(2)为玩家主角类对象创建一个备忘录对象(其中保存了当前主角类对象中的必要信息)
    //FighterMemento* p_fighterMemo = p_fighter->createMomento();
    FCareTaker* pfcaretaker = new FCareTaker(p_fighter->createMomento());

    //(3)玩家与BOSS开始战斗
    cout << "玩家主角与BOSS开始进行激烈的战斗------" << endl;
    p_fighter->setToDead();   //玩家主角在与BOSS战斗中,生命值最终变成0而死亡(被BOSS击败)
    p_fighter->displayInfo(); //显示玩家主角在与BOSS战斗之后的信息

    //(4)因为在与BOSS战斗之前已经通过NPC保存了游戏进度,这里模拟载入游戏进度,恢复玩家主角类对象的数据,让其可以与BOSS再次战斗
    cout << "玩家主角通过备忘录恢复自己的信息------" << endl;
    //p_fighter->restoreMomento(p_fighterMemo);
    p_fighter->restoreMomento(pfcaretaker->getMemento());
    p_fighter->displayInfo(); //显示玩家主角通过备忘录恢复到战斗之前的信息

    //(5)释放资源
    //delete p_fighterMemo;
    delete pfcaretaker->getMemento();
    delete pfcaretaker; //新增
    delete p_fighter;

    Fighter* p_fighter2 = new Fighter(800, 200, 300);
    FCareTaker2* pfcaretaker2 = new FCareTaker2();

    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第一次快照,生命值为800
    p_fighter2->setToDead(); // 改变玩家主角的生命值
    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第二次快照,生命值为0

    p_fighter2->displayInfo(); // 当前生命值为0
    cout << "------------------" << endl;

    // 恢复第一次快照,生命值恢复为800
    p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
    p_fighter2->displayInfo(); // 玩家主角生命值应恢复为800

    // 释放资源
    delete p_fighter2;
    delete pfcaretaker2;

    return 0;
}

3. 备忘录模式 UML 图

备忘录模式 UML 图

UML 图解析

备忘录模式的 UML 图中包含3种角色:

  • Originator (原发器):
    • 原发器是一个普通的业务类,它负责创建备忘录以保存自身的当前内部状态。后续,原发器可以使用备忘录来恢复其内部状态。原发器可以根据需要决定备忘录将存储哪些内部状态。这里指 Fighter 类实现。
  • Memento (备忘录):
    • 备忘录是一个对象,用于存储原发器在某个时刻的内部状态。备忘录的设计通常会参考原发器的设计。为了保护备忘录中的信息不被外部访问,除了创建备忘录的原发器外,其他对象不应直接使用或修改备忘录。因此,备忘录的接口一般使用 private 修饰,并将原发器类设置为友元类。这样可以避免暴露原发器管理的信息,使得备忘录成为一个被动的存储结构。这里指FighterMemento 类。
  • Caretaker (负责人/管理者):
    • 负责人负责保存备忘录,并可以将备忘录传递给其他对象,但不需要了解备忘录的具体细节,也不能对备忘录中的内容进行操作或检查。负责人的主要职责是管理备忘录的生命周期,确保其有效性和正确性。这里指 FCareTaker 类实现。

4. 备忘录模式的优点

  • 封装性: 备忘录模式将对象的状态封装在备忘录中,使得外部无法访问对象的内部状态。
  • 简化恢复操作: 通过备忘录,可以方便地恢复对象到之前的状态,而不需要了解对象的具体实现细节。
  • 历史记录管理: 可以轻松实现对象状态的历史记录功能。

5. 备忘录模式的缺点

  • 内存消耗: 如果对象状态庞大或频繁创建备忘录,可能导致内存消耗增大。
  • 状态管理复杂性: 在需要管理多个状态时,备忘录的数量可能会迅速增加,管理起来会变得复杂。

6. 备忘录模式适用场景

  • 文本编辑器: 保存文档的历史状态,以便用户能够撤销和重做操作。
  • 游戏存档: 在游戏中保存玩家的状态,以便在需要时恢复。
  • 事务处理: 在数据库操作中保存事务的状态,以支持回滚操作。

总结

备忘录模式是一种有效的设计模式,能够帮助开发者管理对象状态的保存与恢复。在需要保留对象历史状态的应用场景中,备忘录模式提供了一种高效、简单的解决方案。

完整代码

#include <iostream>
#include <vector>

using namespace std;

//玩家主角相关的备忘录类
class FighterMemento
{
private:
    //构造函数,用private修饰以防止在外部被随意创建
    FighterMemento(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

private:
    //提供一些供Fighter类来访问的接口,用private修饰防止被任意类访问
    friend class Fighter; //友元类Fighter可以访问本类的私有成员函数
    int getLife() const { return m_life; }
    void setLife(int life) { m_life = life; }
    int getMagic() const { return m_magic; }
    void setMagic(int magic) { m_magic = magic; }
    int getAttack() const { return m_attack; }
    void setAttack(int attack) { m_attack = attack; }
private:
    //玩家主角类中要保存起来的数据,就放到这里来
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
};


//玩家主角类
class Fighter
{
public:
    //构造函数
    Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}


public:
    //将玩家数据写入备忘录(创建备忘录,并在其中存储了当前状态)
    FighterMemento* createMomento()
    {
        return new FighterMemento(m_life, m_magic, m_attack);
    }
    //从备忘录中恢复玩家数据
    void restoreMomento(FighterMemento* pfm)
    {
        m_life = pfm->getLife();
        m_magic = pfm->getMagic();
        m_attack = pfm->getAttack();
    }
    //为测试目的引入的接口,设置玩家的生命值为0(玩家死亡)
    void setToDead()
    {
        m_life = 0;
    }
    //用于输出一些信息
    void displayInfo()
    {
        cout << "玩家主角当前的生命值、魔法值、攻击力分别为:" << m_life << "," << m_magic << "," << m_attack << endl;
    }


private:
    //角色属性
    int m_life;    //生命值
    int m_magic;   //魔法值
    int m_attack;  //攻击力
    //......其他数据略
};

//---------------------
//管理者(负责人)类
class FCareTaker
{
public:
    //构造函数
    FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

    //获取指向备忘录对象的指针
    FighterMemento* getMemento()
    {
        return m_pfm;
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfm = ptmpfm;
    }
private:
    FighterMemento* m_pfm; //指向备忘录对象的指针
};

//-----------------
//支持多个快照的负责人(管理者)类
class FCareTaker2
{
public:
    //析构函数用于释放资源
    ~FCareTaker2()
    {
        for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
        {
            delete (*iter);
        } //end for
    }
    //保存指向备忘录对象的指针
    void setMemento(FighterMemento* ptmpfm)
    {
        m_pfmContainer.push_back(ptmpfm);
    }
    //获取指向备忘录对象的指针
    FighterMemento* getMemento(int index)
    {
        auto iter = m_pfmContainer.begin();
        for (int i = 0; i <= index; ++i)
        {
            if (i == index)
                return (*iter);
            else
                ++iter;
        } //end for
        return nullptr;
    }
private:
    //存储备忘录对象指针的容器
    vector<FighterMemento*> m_pfmContainer;  //#include <vector>
};

int main()
{

    Fighter* p_fighter = new Fighter(800, 200, 300);
    //(1)显示玩家主角在与BOSS战斗之前的信息
    p_fighter->displayInfo();

    //(2)为玩家主角类对象创建一个备忘录对象(其中保存了当前主角类对象中的必要信息)
    //FighterMemento* p_fighterMemo = p_fighter->createMomento();
    FCareTaker* pfcaretaker = new FCareTaker(p_fighter->createMomento());

    //(3)玩家与BOSS开始战斗
    cout << "玩家主角与BOSS开始进行激烈的战斗------" << endl;
    p_fighter->setToDead();   //玩家主角在与BOSS战斗中,生命值最终变成0而死亡(被BOSS击败)
    p_fighter->displayInfo(); //显示玩家主角在与BOSS战斗之后的信息

    //(4)因为在与BOSS战斗之前已经通过NPC保存了游戏进度,这里模拟载入游戏进度,恢复玩家主角类对象的数据,让其可以与BOSS再次战斗
    cout << "玩家主角通过备忘录恢复自己的信息------" << endl;
    //p_fighter->restoreMomento(p_fighterMemo);
    p_fighter->restoreMomento(pfcaretaker->getMemento());
    p_fighter->displayInfo(); //显示玩家主角通过备忘录恢复到战斗之前的信息

    //(5)释放资源
    //delete p_fighterMemo;
    delete pfcaretaker->getMemento();
    delete pfcaretaker; //新增
    delete p_fighter;

    Fighter* p_fighter2 = new Fighter(800, 200, 300);
    FCareTaker2* pfcaretaker2 = new FCareTaker2();

    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第一次快照,生命值为800
    p_fighter2->setToDead(); // 改变玩家主角的生命值
    pfcaretaker2->setMemento(p_fighter2->createMomento()); // 第二次快照,生命值为0

    p_fighter2->displayInfo(); // 当前生命值为0
    cout << "------------------" << endl;

    // 恢复第一次快照,生命值恢复为800
    p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
    p_fighter2->displayInfo(); // 玩家主角生命值应恢复为800

    // 释放资源
    delete p_fighter2;
    delete pfcaretaker2;

    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(1)冒泡排序和其优化

一 冒泡排序 1.1 冒泡排序概念 冒泡排序&#xff08;Bubble Sort&#xff09;是一种交换排序&#xff0c;基本思想是&#xff1a;两两比较相邻记录的关键字&#xff0c;如果反序则交换&#xff0c;直到没有反序记录位置。 假设要对无序数列{2,3,4,5,6,7,8,1}排序&#xff1a;…

从一到无穷大 #34 从Columnar Storage Formats评估到时序存储格式的设计权衡

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言Parquet / ORC功能与结构对比差异Indexes and Filters压缩影响 TsFile总结 引言 …

使用 Milvus Lite、Llama3 和 LlamaIndex 搭建 RAG 应用

大语言模型&#xff08;LLM&#xff09;已经展示出与人类交互并生成文本响应的卓越能力。这些模型可以执行各种自然语言任务&#xff0c;如翻译、概括、代码生成和信息检索等。 为完成这些任务&#xff0c;LLM 需要基于海量数据进行预训练。在这个过程中&#xff0c;LLM 基于给…

捷达千里江山首发亮相,捷达品牌2024成都车展继续宠粉不停

2024年8月30日&#xff0c;捷达品牌携新车捷达千里江山惊艳亮相2024成都国际车展&#xff0c;并在五周年之际&#xff0c;发布幸福包油计划等宠粉福利&#xff0c;号召用户打卡千里江山&#xff0c;奔赴美好。与此同时&#xff0c;全新捷达VS5/VS7五周年纪念版车型进一步降低了…

基于Java+SpringBoot+Vue的汽车销售网站

基于JavaSpringBootVue的汽车销售网站 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345; 某信 gzh 搜索【智能编程小助手】获取项…

Kevin‘s notes about Qt---Episode 3 在界面中修改程序参数Demo

Demo 效果 实现一个加法器,在输入框中分别填入a和b的值,点击“calculate”按钮,在sum处显示a+b的结果。 整体结构 我自己先写了一个模板用于测试从文本框获取输入数据,整个工程的结构如下: 说明: func_myself.h和func_myself.cpp是我自己创建的头文件和源文件,用于定…

江协科技stm32————11-2 W25Q64简介

W25Q64简介 存储容量&#xff08;24位地址&#xff09;在分配地址时需要3个字节&#xff0c;最多16MByte 硬件电路 HOLD&#xff1a;相当于SPI进了一次中断&#xff0c;保持原来的数据 括号内的IO0、IO1……用于双重和四重SPI 框图 右上角首先将8Mb的存储空间以64kb的大小分为…

创新大赛决赛:如何让你的项目更上一层楼?

创新大赛决赛&#xff1a;如何让你的项目更上一层楼&#xff1f; 前言突出项目的核心价值指导老师的辅导作用制作优秀的PPT演讲和答辩的准备利用数据和案例增强说服力模拟答辩的重要性总结结语 前言 在当今这个快速变化的时代&#xff0c;创新不仅是推动社会进步的动力&#xf…

R语言 | 文件读取

一、文件读取 -scan()函数 scan(file “”, what double(), nmax -1, n -1, sep “ ”)&#xff0c;file" " 的双引号里写文件地址&#xff0c;what写读入的数据类型&#xff0c;如果文件有好几种类型&#xff0c;可以啥也不写&#xff08;what" "&…

数据结构与算法——Java实现 3.二分查找——Java版

放下不切实际的幻想&#xff0c;放下无法更改的过去&#xff0c;行云流水&#xff0c;任其行之 —— 24.8.31 一、二分查找——Java基础版 Java中的API——Arrays.binarySearch(数组&#xff0c;目标值) 返回的结果是插入点的位置 若在目标数组中找不到元素&#xff0c;则返…

折叠cell的学习

折叠cell 文章目录 折叠cell前言示例代码部分实现思路核心内容 前言 笔者在暑假的3GShare的项目中就写了有关折叠cell的内容&#xff0c;这里笔者重新讲一下相关内容。 示例 这里先给出效果图&#xff0c;这里是我们的折叠cell的最后的实现效果&#xff0c;下面来讲解一下相关代…

linux 云主机 pip 安装配置 letsencrypt certbot 为多个域名生成免费 https 证书实录

本文记录了我在华为云 EulerOS linux 云主机使用 python pip 方式安装配置 Let’s Encrypt certbot, 并为我的网站的多个域名生成免费 https 证书的整个过程, 包括 python 环境配置, 下载 certbot 及 certbot-nginx, 一次性生成多个域名的证书及注意事项, 以及最后配置 certbot…

k3s中使用GPU资源

前提是已经安装了nvidia驱动 一、安装nvidia-container-toolkit&#xff08;推荐&#xff09; #复制一份docker配置文件,以防被覆盖 cp /etc/docker/daemon.json /etc/docker/daemon.json.bak #安装NVIDIA Container Toolkitapt-get install nvidia-container-toolkit二、配置…

Python 数据分析笔记— Numpy 基本操作(上)

文章目录 学习内容&#xff1a;一、什么是数组、矩阵二、创建与访问数组三、矩阵基本操作 学习内容&#xff1a; 一、什么是数组、矩阵 数组&#xff08;Array&#xff09;&#xff1a;是有序的元素序列&#xff0c;可以是一维、二维、多维。 array1 [1,2,3] 或[a, b, c, d…

如何通过住宅代理进行高效SSL检查

引言 什么是SSL检查&#xff1f;有哪些内容&#xff1f; 为什么要使用SSL检查&#xff1f; SSL检查是如何进行的&#xff1f; 总结 引言 在现代互联网环境中&#xff0c;SSL/TLS协议已成为确保网络通信安全的基石。随着网络攻击手段的不断演进&#xff0c;仅仅依赖于基础的…

[Leetcode 47][Medium]-全排列 II-回溯(全排列问题)

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 和上一道Leetcode46相比&#xff0c;有变化的地方是要排除重复组合的情况。那么在组合问题中去除重复组合的方法是先对数组进行排序,然后在回溯函数中判断当前元素与上一个元素是否相同,若相同…

Kotaemon:开源的RAG UI

检索增强生成 (RAG) 已成为一种改变游戏规则的方法&#xff0c;可增强大型语言模型的功能。Kotaemon 是由 Cinnamon 开发的开源项目&#xff0c;它站在这项创新的最前沿&#xff0c;提供了一个简洁、可定制且功能丰富的基于 RAG 的用户界面&#xff0c;用于与文档聊天。 Kotae…

【再回顾面向对象】,关键字Satic、final

再回顾面对对象 object&#xff1a;所有类的祖先&#xff0c;所有类的方法 GC&#xff1a;垃圾回收站 一般不会回收对象——Car c new Car(); toString() Hashchde要跟地址对应 尽量不要自己产生跟指针不一样&#xff0c;指针是直接指向地址像是数组的索引找的时候可能会有…

AcWing 902. 最短编辑距离

视频讲解&#xff1a; 【E07 线性DP 编辑距离】 两套代码的字符串存储数组都是从1开始存储的&#xff01;&#xff01;&#xff01;&#xff01; 硬套公式&#xff1a; #include<iostream> #include<algorithm> const int N 1010; using namespace std; int …

文心快码前端工程师观点分享:人机协同新模式的探索之路(一)

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; 本系列视频来自百度工程效能部的前端研发经理杨经纬&#xff0c;她在由开源中国主办的“AI编程革新研发效能”OSC源创会杭州站1…