C++ 设计模式——观察者模式

news2024/9/20 20:34:34

观察者模式

    • 观察者模式
      • 主要组成部分
      • 例一:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题
        • 第四步:实现具体观察者
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 例二:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题(玩家类)
        • 第四步:实现具体观察者(聊天通知器)
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 优缺点
      • 适用场景
      • 例一:完整代码
      • 例二:完整代码

观察者模式

观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

引入“观察者”设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。

主要组成部分

  1. 主题(Subject)

    • 也叫观察目标,指被观察的对象。
    • 维护观察者列表,提供添加、删除观察者的方法。
    • 当状态变化时,通知所有的观察者。
  2. 观察者(Observer)

    • 定义一个更新接口,以便主题在状态变化时调用。
    • 每个观察者都可以根据主题的变化更新自身状态。
  3. 具体主题(ConcreteSubject)

    • 实现主题的接口,维护具体的状态。
    • 在状态变化时,调用通知方法来更新所有观察者。
  4. 具体观察者(ConcreteObserver)

    • 实现观察者接口,更新自身状态以反映主题的变化。
    • 每个观察者可以根据主题的状态执行特定的操作。

例一:工作流程

  1. 观察者注册到主题:在主函数中,使用 attach 方法将观察者注册到主题。
  2. 主题的状态发生变化:调用 setState 方法改变主题的状态。
  3. 主题调用通知方法:在 setState 方法中,调用 notify 方法通知所有注册的观察者。
  4. 观察者接收到通知后,更新自身状态:每个观察者实现 update 方法,接收到通知后更新自身状态并输出。
第一步:定义观察者接口

首先,定义一个观察者接口,所有观察者都需要实现这个接口。

// 观察者接口
class Observer {
public:
    virtual void update(int state) = 0; // 更新接口
};
第二步:定义主题接口

接下来,定义一个主题接口,主题需要维护观察者列表,并提供添加、删除观察者的方法。

// 主题接口
class Subject {
public:
    virtual void attach(Observer* observer) = 0; // 添加观察者
    virtual void detach(Observer* observer) = 0; // 移除观察者
    virtual void notify() = 0; // 通知观察者
};
第三步:实现具体主题

实现一个具体主题类,维护观察者列表和状态,并在状态变化时通知观察者。

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove

// 具体主题
class ConcreteSubject : public Subject {
private:
    std::vector<Observer*> observers; // 观察者列表
    int state; // 主题的状态

public:
    void attach(Observer* observer) override {
        observers.push_back(observer); // 添加观察者
    }

    void detach(Observer* observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者
    }

    void notify() override {
        for (Observer* observer : observers) {
            observer->update(state); // 通知所有观察者
        }
    }

    void setState(int newState) {
        state = newState; // 更新状态
        notify(); // 状态变化时通知观察者
    }

    int getState() const {
        return state; // 获取当前状态
    }
};
第四步:实现具体观察者

实现观察者类,更新自身状态以反映主题的变化。

// 具体观察者
class ConcreteObserver : public Observer {
private:
    std::string name; // 观察者名称
    int observedState; // 观察者的状态

public:
    ConcreteObserver(const std::string& name) : name(name) {}

    void update(int state) override {
        observedState = state; // 更新观察者的状态
        std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;
    }
};
第五步:主函数

在主函数中,创建主题和观察者对象,注册观察者,并演示状态变化。

// 主函数
int main() {
    ConcreteSubject subject; // 创建具体主题

    ConcreteObserver observer1("Observer 1"); // 创建观察者1
    ConcreteObserver observer2("Observer 2"); // 创建观察者2

    subject.attach(&observer1); // 注册观察者1
    subject.attach(&observer2); // 注册观察者2

    subject.setState(1);  // 状态变化,通知所有观察者
    subject.setState(2);  // 状态变化,通知所有观察者

    return 0;
}
UML 图

观察者模式 UML 图一描述

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Subject 类。提供增加和删除观察者对象的接口,如 attachdetach

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 ConcreteSubject 类。

Observer(观察者):定义了观察者的接口,所有具体观察者都需要实现这个接口。当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Observer 类。

ConcreteObserver(具体观察者):实现了观察者接口,并注册到主题。当具体目标状态发生变化时,自身会接到通知。这里指 ConcreteObserver 类。

例二:工作流程

下面是逐步实现游戏聊天系统,使用观察者模式来管理玩家之间的聊天信息,每个玩家可以选择加入或退出家族聊天,系统会自动处理通知。将按照设计思路,逐步构建代码。

  1. 观察者注册到被观察者:在主函数中,使用 addToList 方法将玩家注册到聊天通知器。
  2. 被观察者的状态发生变化:调用 SayWords 方法改变聊天内容。
  3. 被观察者调用通知方法:在 SayWords 方法中,调用 notify 方法通知所有注册的玩家。
  4. 观察者接收到通知后,更新自身状态:每个玩家实现 NotifyWords 方法,接收到通知后输出聊天信息。
第一步:定义观察者接口

首先,定义一个观察者接口,所有玩家都需要实现这个接口,以接收聊天信息的更新通知。

// 观察者接口
class Notifier {
public:
    virtual void addToList(Fighter* player) = 0; // 添加玩家
    virtual void removeFromList(Fighter* player) = 0; // 移除玩家
    virtual void notify(Fighter* talker, const std::string& message) = 0; // 通知玩家
    virtual ~Notifier() {}
};
第二步:定义主题接口

接下来,定义一个定义主题接口,玩家需要维护聊天通知器,并提供添加、删除玩家的方法。

// 被观察者接口
class Fighter {
public:
    virtual void NotifyWords(Fighter* talker, const std::string& message) = 0; // 更新接口
    virtual void SetFamilyID(int id) = 0; // 设置家族ID
    virtual int GetFamilyID() const = 0; // 获取家族ID
    virtual ~Fighter() {}
};
第三步:实现具体主题(玩家类)

实现一个具体玩家类,维护玩家名称和家族ID,并在收到聊天信息时更新状态。

#include <iostream>
#include <string>
#include <list>
#include <map>
#include <algorithm> // 用于 std::remove

// 具体主题:玩家
class ConcreteFighter : public Fighter {
private:
    std::string name; // 玩家名称
    int familyID; // 家族ID

public:
    ConcreteFighter(const std::string& playerName) : name(playerName), familyID(-1) {}

    void SetFamilyID(int id) override {
        familyID = id;
    }

    int GetFamilyID() const override {
        return familyID;
    }

    void NotifyWords(Fighter* talker, const std::string& message) override {
        std::cout << "玩家 " << name << " 收到了来自 " << talker->GetFamilyID() << " 的消息: " << message << std::endl;
    }
};
第四步:实现具体观察者(聊天通知器)

实现聊天通知器类,维护玩家列表,并在聊天内容变化时通知玩家。

// 具体观察者:聊天通知器
class TalkNotifier : public Notifier {
private:
    std::map<int, std::list<Fighter*>> familyList; // 家族ID与玩家列表的映射

public:
    void addToList(Fighter* player) override {
        int familyID = player->GetFamilyID();
        if (familyID != -1) {
            familyList[familyID].push_back(player); // 添加玩家到对应家族
        }
    }

    void removeFromList(Fighter* player) override {
        int familyID = player->GetFamilyID();
        if (familyID != -1) {
            familyList[familyID].remove(player); // 移除玩家
        }
    }

    void notify(Fighter* talker, const std::string& message) override {
        int familyID = talker->GetFamilyID();
        if (familyID != -1) {
            for (Fighter* player : familyList[familyID]) {
                player->NotifyWords(talker, message); // 通知所有玩家
            }
        }
    }
};
第五步:主函数

在主函数中,创建玩家和聊天通知器对象,注册玩家,并演示聊天信息的通知过程。

// 主函数
int main() {
    // 创建聊天通知器
    TalkNotifier notifier;

    // 创建玩家
    ConcreteFighter player1("张三");
    player1.SetFamilyID(100);
    ConcreteFighter player2("李四");
    player2.SetFamilyID(100);
    ConcreteFighter player3("王五");
    player3.SetFamilyID(200);

    // 注册玩家到通知器
    notifier.addToList(&player1);
    notifier.addToList(&player2);
    notifier.addToList(&player3);

    // 玩家发送消息
    player1.SayWords("大家集合,准备进攻!", &notifier); // 张三发送消息
    player2.SayWords("听从指挥,前往目标!", &notifier); // 李四发送消息

    // 移除玩家
    notifier.removeFromList(&player2); // 移除李四

    // 再次发送消息
    player1.SayWords("李四已经不在了,继续行动!", &notifier); // 张三发送消息

    return 0;
}
UML 图

观察者模式 UML 图2

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Notifier 类。提供增加和删除观察者对象的接口,如 addToListremoveFromList

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 TalkNotifier 子类。

Observer(观察者):当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Fighter 类。

ConcreteObserver(具体观察者):调用观察目标的 addToList 成员函数将自身加入到观察者列表中,当具体目标状态发生变化时,自身会接到通知(NotifyWords 成员函数会被调用)。这里指 F_WarriorF_Mage 子类。

优缺点

优点

  • 松耦合:观察者和主题之间的关系是松散的,降低了模块间的耦合度。
  • 动态添加观察者:可以在运行时增加或减少观察者。
  • 多对一的通知:一个主题可以通知多个观察者,适合事件驱动的场景。

缺点

  • 通知开销:如果观察者数量较多,通知开销可能较大。
  • 循环依赖:若观察者和主题相互依赖,可能导致循环更新。

适用场景

  • 事件处理系统:GUI框架中,用户操作(如点击按钮)会触发事件,多个组件需要对这些事件做出反应。

  • 数据绑定:在MVC(模型-视图-控制器)架构中,当模型数据变化时,视图需要自动更新以反映最新的数据。

  • 发布-订阅系统:在消息传递系统中,多个订阅者会对特定主题的消息做出反应,发布者只需发布消息而不需要关心订阅者的具体实现。

  • 状态监控:在监控系统中,多个观察者需要监控同一状态(如温度传感器的读数),当状态变化时,所有观察者都能及时获取更新。

  • 社交媒体通知:当用户发布新内容时,所有关注该用户的粉丝会收到更新通知。

例一:完整代码

将以上步骤组合在一起,完整代码如下:

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove

// 观察者接口
class Observer {
public:
    virtual void update(int state) = 0; // 更新接口
};

// 主题接口
class Subject {
public:
    virtual void attach(Observer* observer) = 0; // 添加观察者
    virtual void detach(Observer* observer) = 0; // 移除观察者
    virtual void notify() = 0; // 通知观察者
};

// 具体主题
class ConcreteSubject : public Subject {
private:
    std::vector<Observer*> observers; // 观察者列表
    int state; // 主题的状态

public:
    void attach(Observer* observer) override {
        observers.push_back(observer); // 添加观察者
    }

    void detach(Observer* observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者
    }

    void notify() override {
        for (Observer* observer : observers) {
            observer->update(state); // 通知所有观察者
        }
    }

    void setState(int newState) {
        state = newState; // 更新状态
        notify(); // 状态变化时通知观察者
    }

    int getState() const {
        return state; // 获取当前状态
    }
};

// 具体观察者
class ConcreteObserver : public Observer {
private:
    std::string name; // 观察者名称
    int observedState; // 观察者的状态

public:
    ConcreteObserver(const std::string& name) : name(name) {}

    void update(int state) override {
        observedState = state; // 更新观察者的状态
        std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;
    }
};

// 主函数
int main() {
    ConcreteSubject subject; // 创建具体主题

    ConcreteObserver observer1("Observer 1"); // 创建观察者1
    ConcreteObserver observer2("Observer 2"); // 创建观察者2

    subject.attach(&observer1); // 注册观察者1
    subject.attach(&observer2); // 注册观察者2

    subject.setState(1);  // 状态变化,通知所有观察者
    subject.setState(2);  // 状态变化,通知所有观察者

    return 0;
}

例二:完整代码

#include <iostream>
#include <list>
#include <map>

using namespace std;

class Fighter; //类前向声明
class Notifier //通知器父类
{
public:
    virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加到列表中
    virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除
    virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息
    virtual ~Notifier() {}
};
class Fighter
{
public:
    Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName)//构造函数
    {
        m_iFamilyID = -1; //-1表示没加入任何家族
    }
    virtual ~Fighter() {} //析构函数

public:
    void SetFamilyID(int tmpID)  //加入家族时设置家族ID
    {
        m_iFamilyID = tmpID;
    }
    int GetFamilyID() //获取家族ID
    {
        return m_iFamilyID;
    }

    void SayWords(string tmpContent, Notifier* notifier) //玩家说了某句话
    {
        notifier->notify(this, tmpContent);
    }

    //通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同的动作
    virtual void NotifyWords(Fighter* talker, string tmpContent)
    {
        //显示信息
        cout << "玩家:" << m_sPlayerName << " 收到了玩家:" << talker->m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl;
    }
private:
    int m_iPlayerID; //玩家ID,全局唯一
    string m_sPlayerName;  //玩家名字

    int m_iFamilyID;   //家族ID
};
//“战士”类玩家,父类为Fighter
class F_Warrior :public Fighter
{
public:
    F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};

//“法师”类玩家,父类为Fighter
class F_Mage :public Fighter
{
public:
    F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};

//----------------------------------
class TalkNotifier :public Notifier  //聊天信息通知器
{
public:
    //将玩家增加到家族列表中来
    virtual void addToList(Fighter* player)
    {
        int tmpfamilyid = player->GetFamilyID();
        if (tmpfamilyid != -1) //加入了某个家族
        {
            auto iter = m_familyList.find(tmpfamilyid);
            if (iter != m_familyList.end())
            {
                //该家族id在map中已经存在
                iter->second.push_back(player); //直接把该玩家加入到该家族
            }
            else
            {
                //该家族id在map中不存在
                list<Fighter*> tmpplayerlist;
                m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist));//以该家族id为key,增加条目到map中
                m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家
            }
        }
    }
    //将玩家从家族列表中删除
    virtual void removeFromList(Fighter* player)
    {
        int tmpfamilyid = player->GetFamilyID();
        if (tmpfamilyid != -1) //加入了某个家族
        {
            auto iter = m_familyList.find(tmpfamilyid);
            if (iter != m_familyList.end())
            {
                m_familyList[tmpfamilyid].remove(player);
            }
        }
    }

    //家族中某玩家说了句话,调用该函数来通知家族中所有人
    virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家
    {
        int tmpfamilyid = talker->GetFamilyID();
        if (tmpfamilyid != -1)
        {
            auto itermap = m_familyList.find(tmpfamilyid);
            if (itermap != m_familyList.end())
            {
                //遍历该玩家所属家族的所有成员
                for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist)
                {
                    (*iterlist)->NotifyWords(talker, tmpContent);
                }
            }
        }
    }
private:
    //map中的key表示家族id,value代表该家族中所有玩家列表
    map<int, list<Fighter*> > m_familyList; //增加#include <map>
};


int main()
{
    //创建游戏玩家
    Fighter* pplayerobj1 = new F_Warrior(10, "张三");
    pplayerobj1->SetFamilyID(100);

    Fighter* pplayerobj2 = new F_Warrior(20, "李四");
    pplayerobj2->SetFamilyID(100);

    Fighter* pplayerobj3 = new F_Mage(30, "王五");
    pplayerobj3->SetFamilyID(100);

    Fighter* pplayerobj4 = new F_Mage(50, "赵六");
    pplayerobj4->SetFamilyID(200);

    //创建通知器
    Notifier* ptalknotify = new TalkNotifier();

    //玩家增加到家族列表中来,这样才能收到家族聊天信息
    ptalknotify->addToList(pplayerobj1);
    ptalknotify->addToList(pplayerobj2);
    ptalknotify->addToList(pplayerobj3);
    ptalknotify->addToList(pplayerobj4);

    //某游戏玩家聊天,同族人都应该收到该信息
    pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify);

    cout << "王五不想再收到家族其他成员的聊天信息了---" << endl;
    ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除

    pplayerobj2->SayWords("请大家听从族长的调遣,前往沼泽地!", ptalknotify);

    //释放资源
    delete pplayerobj1;
    delete pplayerobj2;
    delete pplayerobj3;
    delete pplayerobj4;
    delete ptalknotify;

    return 0;
}

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

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

相关文章

rocky linux配置说明

下载&#xff1a; 目前最新版的 v9.4 镜像 Download - Rocky Linux 可以在官网下载&#xff0c;不过挺慢的&#xff0c;可以试试阿里云的https://mirrors.aliyun.com/rockylinux/会稍快点 安装&#xff1a; 其步骤和centos基本一样&#xff0c;其中磁盘分区模式是gpt这个并未…

Ingress Nginx Controller

Kubernetes集群 服务暴露 Nginx Ingress Controller 一、ingress控制器 1.1 ingress控制器作用 &#xff08;类似于slb&#xff0c;做代理服务&#xff09; ingress controller可以为kubernetes 集群外用户访问Kubernetes集群内部pod提供代理服务。 提供全局访问代理访问流…

【大模型LLM第九篇】高效的微调方式:Self-Evolved多样性数据采样

前言 来自阿里巴巴和北京大学的文章&#xff1a;Self-Evolved Diverse Data Sampling for Efficient Instruction Tuning link&#xff1a;https://arxiv.org/pdf/2311.08182 github&#xff1a;https://github.com/OFA-Sys/DiverseEvol 一、摘要 提升大型语言模型的指令遵循能…

鸿蒙Harmony开发——设备发烫问题分析

&#xff1b; 本文字数&#xff1a;4207字 预计阅读时间&#xff1a;25分钟 设备过热问题是影响用户体验和设备性能的重要因素。过热不仅会导致性能下降&#xff0c;还可能损坏硬件。因此&#xff0c;开发者需要及时发现、分析并解决这一问题。本文将首先介绍评估设备过热的关键…

Qt/C++控件实例 QWidget联合动画实现卷轴效果

显示特点 动态翻页效果&#xff1a;数字在更新时&#xff0c;会有一个从前一数字向下一数字过渡的翻页效果。这种过渡动画使得数字变化过程更加平滑和自然&#xff0c;避免了突然的跳变。 高对比度显示&#xff1a;每个数字的背景框颜色为红色&#xff0c;数字颜色为白色&…

每日一个科研绘图·气泡图|24-08-24

一、气泡图 气泡图是一种数据可视化工具&#xff0c;它在传统的二维散点图的基础上增加了一个维度&#xff0c;使得我们能够同时观察三个变量之间的关系。这种图表通过点的大小来表示第三个数值变量的大小&#xff0c;从而提供了一种直观的方式来探索数据中的模式和趋势。 在…

Facebook的AI助手:如何提升用户社交体验的智能化

在现代社交媒体平台中&#xff0c;人工智能&#xff08;AI&#xff09;的应用正逐渐改变人们的社交体验。Facebook作为全球最大的社交媒体平台之一&#xff0c;已在AI技术的开发与应用上投入了大量资源&#xff0c;并通过其AI助手为用户提供了更加个性化、智能化的互动体验。这…

pycharm 隐藏 __ init __ .py 文件

pycharm 隐藏 __ init __ .py 文件 每次写python项目时&#xff0c;都会有一个自动生成__ init __ .py文件&#xff0c;看的很累&#xff0c;所以想把__ init __ .py文件给隐藏掉 方法&#xff1a; &#xff08;1&#xff09;File -> Settings (2) Appearance & Behav…

武汉流星汇聚:全球化布局与本地化运营,亚马逊电商帝国崛起秘诀

在数字时代的浪潮中&#xff0c;亚马逊如同一颗璀璨的星辰&#xff0c;照亮了全球电子商务的天空。作为美国乃至全球访问量最高的电商平台&#xff0c;亚马逊不仅重塑了消费者的购物习惯&#xff0c;更以其独特的商业模式、惊人的订单履行速度和卓越的购物体验&#xff0c;引领…

FFmpeg的入门实践系列四(AVS)

欢迎诸位来阅读在下的博文~ 在这里&#xff0c;在下会不定期发表一些浅薄的知识和经验&#xff0c;望诸位能与在下多多交流&#xff0c;共同努力 文章目录 前期博客参考书籍一、AVS简介二、基于FFmpeg配置AVS2 前期博客 FFmpeg的入门实践系列一(环境搭建) FFmpeg的入门实践系列…

零基础5分钟上手亚马逊云科技-利用MQ为应用解耦

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…

揭秘AI绘画赚钱之道:学会这十大技巧,轻松踏入高薪岗位,实现财务自由!

亲爱的学友们&#xff0c;当我们谈论AI绘画&#xff0c;不仅仅是在描绘科技如何重塑艺术疆界&#xff0c;更是在探索一场颠覆性的商业革命。自从AI绘画技术从萌芽走向繁荣&#xff0c;它已经从简单的图像风格转换跃升为能读懂人心、创世般生成各类视觉作品的神奇力量。今天&…

哈夫曼树和哈夫曼编码详解(包含Java代码实现)

目录 什么是哈夫曼树&#xff1f;如何构造哈夫曼树&#xff1f;构造过程代码实现哈夫曼树的结构构建哈夫曼树并计算WPL值测试代码 什么是哈夫曼编码&#xff1f;如何构建哈夫曼编码&#xff1f;构建过程代码实现 什么是哈夫曼树&#xff1f; 哈夫曼树又称为最优树&#xff0c;是…

应用实例 | Simufact 增材制造工艺仿真助力保时捷薄壁件打印

在过去的 30 多年里&#xff0c;增材制造技术被广泛应用于各行各业&#xff0c;尤其在医疗器械、航空领域尤为突出。其中激光束进行金属粉末床熔融的工艺应用最为广泛&#xff0c;由该工艺制造的零部件普遍兼具高设计自由度、高灵活性、优异机械性能等特点。对于汽车行业&#…

百日筑基第六十天-学习一下Tomcat

百日筑基第六十天-学习一下Tomcat 一、Tomcat 顶层架构 Tomcat 中最顶层的容器是 Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个 Server可以包含至少一个 Service&#xff0c;用于具体提供服务。Service 主要包含两个部分&#xff1a;Conn…

都说25张宇是大趋势,那660、880还刷吗?

25上半年张宇很火&#xff0c;是有客观原因的&#xff1a; 1. 大纲改革后&#xff0c;大题变少了&#xff0c;选填变多了&#xff1b; 2. 但是考试覆盖的知识点不能少&#xff0c; ——因为知识点越少&#xff0c;随机性越高&#xff0c;这个考试就越不公平。 这直接导致了&…

vue组件和插件使用

前端组件 1、安装pinia(Vue 的专属状态管理库)&#xff1a; npm install pinia2、安装pinia-plugin-persistedstate(持久存储插件): npm install pinia-plugin-persistedstate浏览器刷新时&#xff0c;有些数据希望是保存下来的。如用户登录后&#xff0c;用户信息会存储在全…

Scheme5.0标准之重要特性及用法实例(三十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

设置虚拟机使用主机以太网而不是WiF连接

虚拟机使用主机的以太网连接而不是Wi-Fi连接&#xff0c;可以通过在虚拟化软件中配置虚拟机的网络设置来实现。以下是一些常见的虚拟化软件&#xff08;如VMware和VirtualBox&#xff09;中设置虚拟机网络以使用以太网连接的步骤&#xff1a; 一、VMware中设置 1、打开虚拟网…

Python画笔案例-007 绘制水滴

1、绘制水滴 通过 python 的turtle 库绘制一个水滴的图案&#xff0c;如下图&#xff1a; 2、实现代码 今天绘制的水滴&#xff0c;在tuitle 库里并没有直接的功能可以绘制&#xff0c;我们仔细观察&#xff0c;可以看出&#xff0c;水滴往下越来越粗&#xff0c;所以我们可以…