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

设计模式八:观察者模式

文章目录

      • 1、观察者模式
      • 2、示例
      • 3、spring中的观察者模式
        • 3.1 spring观察者模式的使用
        • 3.2 spring观察者模式原理解析

1、观察者模式

观察者模式(Observer Design Pattern),也叫做发布订阅模式(Publish-Subscribe Design Pattern)、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式

观察者允许一个对象将其状态的改变通知其他对象。实际上主要的部分就是观察者和被观察者,比如前言提到的消息发布,就属于被观察者,而各种不同的平台消息提醒,则是一系列的观察者。

模型:
8.1、观察者模型

//观察者接口
public interface Observer {/**声明响应方法,被观察者调用以达到通知的作用*/void response();
}//观察者实现类
public class ConcreteObserver1 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver1");}
}public class ConcreteObserver2 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver2");}
}
//观察者抽象类
public abstract class Subject {//存储注册的观察者protected List<Observer> observerList = new ArrayList<Observer>();/*** 增加观察者* @param observer 观察者*/public void add(Observer observer) {observerList.add(observer);}/*** 注销观察者,从观察者集合中删除一个观察者* @param observer 观察者*/public void remove(Observer observer) {observerList.remove(observer);}/**通知观察者*/public abstract void notifyObserver();}//被观察者实现类
public class ConcreteSubject extends Subject {@Overridepublic void notifyObserver() {System.out.println("遍历观察者:");for (Observer observer : observerList) {observer.response();}}
}
//客户端测试类
public class Client {public static void main(String[] args) {Subject subject = new ConcreteSubject();//项目中,这些内容一般在new Subject对象启动时自动加载subject.add(new ConcreteObserver1());subject.add(new ConcreteObserver2());subject.notifyObserver();}
}

观察者注册到被观察者中,然后通过被观察者调用观察者的方法达到通知的效果

实际中,会有多个观察者,以及多个事件,每个观察者关注不同的事件,待相对应事件发生时,"通知"关注改时间的观察者

2、示例

宝马公司偶尔推出打折或立减活动(8折、9折、立减5000),不同的用户关注不同的活动,如用户1关注8折和9折活动,用户2关注8折和立减5000活动,用户3这些活动都关注,当宝马公司推出一种活动的时候,立刻通知到关注该活动的用户

8.2、观察者模式示例

//活动(事件)
@Getter
public enum EventEnum {eightDisc(1, "8折"),nineDisc(2, "9折"),subFiveThous(3, "立减5000元");private Integer code;private String  value;public static EventEnum getEventEnum(Integer code){for(EventEnum event : values()){if(event.code.equals(code)){return event;}}return null;}public static EventEnum getEventEnum(String value){for(EventEnum event : values()){if(event.getValue().equals(value)){return event;}}return null;}public String getValue(Integer code){for(EventEnum event : values()){if(event.code.equals(code)){return event.getValue();}}return null;}public Integer getCode(String value){for(EventEnum event : values()){if(event.getValue().equals(value)){return event.getCode();}}return null;}EventEnum(Integer code, String value) {this.code = code;this.value = value;}public void setCode(Integer code) {this.code = code;}public void setValue(String value) {this.value = value;}
}
//客户(观察者)
public interface Consumer {void response(EventEnum event);
}
public class ConcreteConsumer1 implements Consumer{@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer1 已知悉 宝马公司" + event.getValue() +" 活动");}
}
public class ConcreteConsumer2 implements Consumer{@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer2 已知悉 宝马公司" + event.getValue() +" 活动");}
}
public class ConcreteConsumer3 implements Consumer {@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer3 已知悉 宝马公司" + event.getValue() +" 活动");}
}
//宝马公司(被观察者)
public abstract class BMWCompany {Map<EventEnum, List<Consumer>> eventMap = new HashMap<>();//客户注册关注事件public BMWCompany(){Consumer consumer1 = new ConcreteConsumer1();Consumer consumer2 = new ConcreteConsumer2();Consumer consumer3 = new ConcreteConsumer3();registerConsumerEvent(consumer1, EventEnum.getEventEnum(1));registerConsumerEvent(consumer1, EventEnum.getEventEnum(2));registerConsumerEvent(consumer2, EventEnum.getEventEnum(2));registerConsumerEvent(consumer2, EventEnum.getEventEnum(3));registerConsumerEvent(consumer3, EventEnum.getEventEnum(1));registerConsumerEvent(consumer3, EventEnum.getEventEnum(2));registerConsumerEvent(consumer3, EventEnum.getEventEnum(3));}public void registerConsumerEvent(Consumer consumer,EventEnum event){List<Consumer> consumers = eventMap.getOrDefault(event, new ArrayList<>());consumers.add(consumer);eventMap.put(event, consumers);}public void removeConsumer(Consumer consumer){for(List<Consumer> consumerList : eventMap.values()){consumerList.remove(consumer);}}public abstract void notifyConsumer(EventEnum event);
}public class DiscountManage extends BMWCompany{@Overridepublic void notifyConsumer(EventEnum event) {List<Consumer> consumers = eventMap.get(event);if(!CollectionUtils.isEmpty(consumers)){consumers.forEach(consumer -> consumer.response(event));}else{System.out.println("无人关注 " + event.getValue() + " 活动");}}
}
//客户端测试类
public class Client {public static void main(String[] args) {BMWCompany discountManage = new DiscountManage();discountManage.notifyConsumer(EventEnum.getEventEnum(3));}
}

该示例中,生成被观察者对象时,构造函数将观察者与其关注的事件放入到map中,key为事件,value为关注该事件的用户;

也就是说由被观察者管理事件和观察者之间的关系:观察者面向被观察者,由被观察者管理;被观察者亲自通知观察者

以上就是观察者模式与发布订阅模式,发布订阅模式有专门的组件管理事件和观察者之间的关系:被观察者发布事件到事件组件,无需关心谁订阅了哪些事件;观察者面向事件组件订阅事件,不关心谁发布的事件

3、spring中的观察者模式

以上示例中我们需要在创建被观察者实例时,自己写代码创建观察者和事件之间的关系,当新增事件和观察者的时候,要在BMWCompany()构造函数中再新增语句;那么可不可以只提供事件、观察者以及观察者所关注的事件,组装的事情由spring自动完成?

3.1 spring观察者模式的使用

示例:平台用户注册成功时,为用户发送邮件并发放优惠券;平台用户销毁账户时,发送邮件和消息

一般模型:
8.3、用户注册模型

//邮件业务接口和实现类
public interface EmailService {void onRegister(String name);void onDestory(String name);
}
public class EmailServiceImpl implements EmailService {@Overridepublic void onRegister(String name) {log.info("邮件: 尊敬的 " + name + " 先生/女士,恭喜注册成功");}@Overridepublic void onDestory(String name) {log.info("邮件: 尊敬的 " + name + " 先生/女士,很遗憾,您销毁账号");}
}//优惠券业务接口和实现类
public interface CouponService {void onRegister(String name);
}
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {@Overridepublic void onRegister(String name) {log.info("优惠券: 尊敬的 " + name + " 先生/女士,恭喜注册成功,赠送您100元代金券");}
}//消息业务接口和实现类
public interface MessageService {void onDestory(String name);
}
@Slf4j
@Service
public class MessageServiceImpl implements MessageService {@Overridepublic void onDestory(String name) {log.info("信息: 尊敬的 " + name + " 先生/女士,很遗憾,您销毁账号");}
}
//用户业务接口和实现类
public interface UserService {void register(String name);void destroy(String name);
}
@Slf4j
@Service
public class UserServiceImpl implements UserService {@AutowiredEmailService emailService;@AutowiredMessageService messageService;@AutowiredCouponService couponService;@Overridepublic void register(String name) {//执行各种校验动作,入库操作//doRegister(name);log.info(name + " 的注册逻辑......");//发送邮件emailService.onRegister(name);//发送优惠券couponService.onRegister(name);}@Overridepublic void destroy(String name) {//执行销毁账号操作//doDestory(name);log.info(name + " 的销毁逻辑......");//发送邮件emailService.onDestory(name);//发送短信messageService.onDestory(name);}
}

如上所示,如果订阅者很多,那么在用户业务实现类中要添加所有的相关订阅者引用,并且在方法中通知所有对应的订阅者

spring中发布订阅模式:

8.4、spring发布订阅

//注册事件
public class UserRegisterEvent extends ApplicationEvent {private String username;public UserRegisterEvent(Object source) {super(source);}public UserRegisterEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}
}//销毁事件
public class UserDestoryEvent extends ApplicationEvent {private String username;public UserDestoryEvent(Object source) {super(source);}public UserDestoryEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}
}
//邮件业务实现类
@Slf4j
@Service
public class EmailServiceImpl implements EmailService {//实现监听者(订阅者)的一种方式---方法上添加@EventListener注解,可实现一个类中订阅多个事件@EventListenerpublic void onRegister(UserRegisterEvent userRegisterEvent) {log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功");}@EventListenerpublic void onDestory(UserRegisterEvent userRegisterEvent) {log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");}
}//优惠券业务实现类
@Slf4j
@Service
public class CouponServiceImpl implements CouponService, ApplicationListener<UserRegisterEvent> {//第二种实现监听者(订阅者)的方式,实现ApplicationListener接口的onApplicationEvent方法@Overridepublic void onApplicationEvent(UserRegisterEvent userRegisterEvent) {log.info("优惠券: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功,赠送您100元代金券");}
}//消息业务实现类
@Slf4j
@Service
public class MessageServiceImpl implements MessageService, ApplicationListener<UserDestoryEvent> {@Overridepublic void onApplicationEvent(UserDestoryEvent userDestoryEvent) {log.info("信息: 尊敬的 " + userDestoryEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");}
}
//用户业务实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService, ApplicationEventPublisherAware {private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(@NotNull ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}@Overridepublic void register(String name) {//执行各种校验动作,入库操作//doRegister(name);log.info(name + " 的注册逻辑......");//发布注册事件applicationEventPublisher.publishEvent(new UserRegisterEvent(this, name));}@Overridepublic void destroy(String name) {//执行销毁账号操作//doDestory(name);log.info(name + " 的销毁逻辑......");//发布销毁事件applicationEventPublisher.publishEvent(new UserDestoryEvent(this, name));}
}

由上所示,我们只需要编写事件、订阅者逻辑即可,具体的订阅者和事件之间的关系有spring来建立关联

每当事件发生时,spring获取订阅该事件的类去执行相对应的处理方法

3.2 spring观察者模式原理解析

当发布者执行applicationEventPublisher.publishEvent(new UserRegisterEvent(this, name));时,spring会一直执行到SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType)方法

public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);Executor executor = this.getTaskExecutor();Iterator var5 = this.getApplicationListeners(event, type).iterator();while(var5.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var5.next();if (executor != null) {executor.execute(() -> {this.invokeListener(listener, event);});} else {this.invokeListener(listener, event);}}
}

由上可知,spring也是通过事件获取所有关注该事件的监听器,依次执行订阅处理逻辑

具体spring执行逻辑参考 Spring观察监听器-ApplicationEventPublisher的publishEvent实现异步事件解耦业务

相关文章:

设计模式八:观察者模式

文章目录 1、观察者模式2、示例3、spring中的观察者模式3.1 spring观察者模式的使用3.2 spring观察者模式原理解析 1、观察者模式 观察者模式&#xff08;Observer Design Pattern&#xff09;,也叫做发布订阅模式&#xff08;Publish-Subscribe Design Pattern&#xff09;、模…...

黑马程序员java部分笔记(持续更新)十点二:封装

面向对象的三大特征&#xff1a;封装&#xff0c;继承&#xff0c;多态 告诉我们正确的属性与方法 例1&#xff1a;需求&#xff1a;定义一个类&#xff1a;人 属性&#xff1a;姓名&#xff0c;年龄 行为&#xff1a;吃饭&#xff0c;睡觉 代码&#xff1a; public class Pe…...

ChatGPT-Next-Web SSRF漏洞+XSS漏洞复现(CVE-2023-49785)

0x01 产品简介 ChatGPT-Next-Web 是一种基于 OpenAI 的 GPT-3.5 、GPT-4.0语言模型的产品。它是设计用于 Web 环境中的聊天机器人,旨在为用户提供自然语言交互和智能对话的能力。 0x02 漏洞概述 2024年3月,互联网上披露CVE-2023-49785 ChatGPT-Next-Web SSRF/XSS 漏洞,未经…...

【小黑嵌入式系统第十九课】结课总结(三)——操作系统部分(RTOSμC/OS-Ⅲ程序设计基础(任务函数时间临界区通信))

上一课&#xff1a; 【小黑嵌入式系统第十八课】结课总结&#xff08;二&#xff09;——软件部分&#xff08;系统架构&调试&测试&运行&系统软件设计&#xff09; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0…...

C# Onnx C2PNet 图像去雾 室内场景

目录 介绍 效果 模型信息 项目 代码 下载 C# Onnx C2PNet 图像去雾 室内场景 介绍 github地址&#xff1a;GitHub - YuZheng9/C2PNet: [CVPR 2023] Curricular Contrastive Regularization for Physics-aware Single Image Dehazing [CVPR 2023] Curricular Contrasti…...

工作中Git如何切换远程仓库地址

工作中Git如何切换远程仓库地址 部门之前的仓库不用了&#xff0c;重新建了一个仓库&#xff0c;但是上传代码还是上传到了之前的仓库里面了&#xff0c;所以得进行修改&#xff0c;下面将修改地址的方法进行操作。 方法一、直接修改远程仓库地址 查看当前远程仓库地址 git …...

香港理工大学主办!2024年第八届电力能源系统与应用国际会议(ICoPESA 2024)即将召开!

2024年第八届电力能源系统与应用国际会议&#xff08;ICoPESA 2024&#xff09; 2024年6月24日-26日 中国香港 ICoPESA 2024-Hong Kong (icpesa.org)https://icpesa.org/index.html 会议组织单位 会议出版及检索&#xff1a; 会议录用并注册的论文将由IEEE出版&#xff0c;…...

【微服务-Nacos】Nacos集群的工作原理及集群间数据同步过程

上篇文章我们介绍了Nacos集群的搭建方法及步骤&#xff0c;下面我们来看一下Nacos集群的工作原理&#xff0c;一共有两部分&#xff1a;Leader节点选举及各节点数据同步。 1、Nacos集群中Leader节点是如何产生的 Nacos集群采用了Raft算法实现。它是一种比较简单的选举算法&am…...

LeetCode202.快乐数

202快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果这个过程 结果为 1&…...

c++面试整理(二)

一、new和malloc的区别 1.属性: new属于c运算符&#xff0c;编译器支持就可以&#xff0c;makkoc是c的标准库函数&#xff0c;需要引用头文件才可以调用。 2.参数和返回值 malloc分配内存时需要指定内存大小&#xff0c;返回值是void*的指针&#xff0c;需要强制转换 new根…...

Python中的区块链技术与应用

区块链技术是一个复杂的概念&#xff0c;涉及许多不同的方面&#xff0c;如加密算法、数据结构、网络协议等。在这里&#xff0c;我将提供一个简单的区块链实现示例&#xff0c;以帮助你理解其基本概念。请注意&#xff0c;这个示例是为了教学目的而简化的&#xff0c;并不适用…...

opencv-python 霍夫变换圆形检测:HoughCircles

文章目录 简介代码HoughCircles函数说明 简介 opencv中提供了基于霍夫变换的圆形检测方法&#xff0c;可实现下图所示的检测结果。 其中&#xff0c;【gray】是经过均值滤波的灰度图&#xff0c;其目的是将目标边缘凸显出来&#xff1b;【edge】是通过Canny边缘检测得到的灰度…...

行为型-观察者模式

文章目录 基本概念定义使用场景代码实现 延伸阅读java监听机制spring监听机制 基本概念 定义 观察者模式是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;其所有依赖者都会收到通知并自动更新。 观察者模式…...

《ElementPlus 与 ElementUI 差异集合》el-input 和 el-button 属性 size 有变化

差异 ** element-ui el-input、el-input-number 和 el-button 中&#xff0c;属性size 值是 medium / small / minielement-plus el-input、el-input-number 和 el-button 中&#xff0c;属性size 值是 large / default /small 如果你是自动升级&#xff0c;Vue3 系统会有如…...

pxe安装mini centos系统

一、准备工作 1、关闭防火墙和selinux systemctl stop firewalld && systemctl disable firewalldsetenforce 02、配置静态ip 需要在dhcp里面填写tftp配置&#xff0c;所以需要固定ip 二、dhcp安装配置 作用&#xff1a;给客户端提供ip地址&#xff0c;并告诉客户…...

Android studio 性能调试

一、概述 Android studio 的Profiler可用来分析cpu和memory问题&#xff0c;下来进行说明介绍。 二、Android studio CPU调试 从开发模拟器或设备中启动应用程序&#xff1b; 在 Android Studio 中&#xff0c;通过选择View > Tool Windows > Profiler启动分析器。 应…...

java8特性 stream流中map函数的使用

map 函数的作用就是针对管道流中的每一个数据元素进行转换操作。 例如 将集合中的每一个字符串&#xff0c;全部转换成大写&#xff01; List<String> collect alpha.stream().map(String::toUpperCase).collect(Collectors.toList()); //上面使用了方法引用&#xf…...

【Emgu CV教程】9.5、形态学常用操作之形态学梯度

文章目录 一、相关概念1.什么叫形态学梯度2.形态学梯度的函数 二、演示1.原始素材2.代码3.运行结果 一、相关概念 1.什么叫形态学梯度 形态学梯度&#xff0c;就是用膨胀的原始图像减去腐蚀的原始图像&#xff0c;所以它的特性就是去除前景物体的内部区域&#xff0c;只得到前…...

算法笔记之蓝桥杯pat系统备考(2)

算法笔记之蓝桥杯&pat系统备考&#xff08;1&#xff09; 文章目录 五、数学问题5.2最大公约数和最小公倍数5.2.1最大公约数5.2.2最小公倍数 5.3分数的四则运算5.3.1分数的表示与化简5.3.2分数的四则运算5.3.3分数的输出 5.4素数&#xff08;质数&#xff09;5.4.1[素数的…...

基于SpringBoot+Druid实现多数据源:注解+编程式

前言 本博客姊妹篇 基于SpringBootDruid实现多数据源&#xff1a;原生注解式基于SpringBootDruid实现多数据源&#xff1a;注解编程式基于SpringBootDruid实现多数据源&#xff1a;baomidou多数据源 一、功能描述 配置方式&#xff1a;配置文件中配置默认数据源&#xff0c…...

MyBatis-Plus 大表分页 count () 性能瓶颈深度解析

在使用MyBatis-Plus进行大表分页查询时&#xff0c;你是否通过日志发现&#xff0c;分页插件总会先执行一条count()语句&#xff0c;且这条count()在千万级数据下耗时极长&#xff0c;严重拖慢整体响应&#xff1f;本文将从源码层面剖析MyBatis-Plus分页count()的执行机制&…...

5分钟掌握ViGEmBus:Windows虚拟手柄驱动的完整指南

5分钟掌握ViGEmBus&#xff1a;Windows虚拟手柄驱动的完整指南 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus ViGEmBus是一款专业的Windows内核级虚拟游戏手…...

告别环境配置烦恼:在Windows上通过VSCode与ESP-IDF快速搭建ESP32开发环境

1. 为什么选择VSCodeESP-IDF开发ESP32&#xff1f; 作为一个从Arduino转向ESP32开发的过来人&#xff0c;我深刻理解新手在环境配置上的痛苦。传统方法需要手动安装Python、Git、交叉编译工具链等十多个组件&#xff0c;光是处理环境变量冲突就能让人崩溃一整天。直到发现VSCod…...

万象视界灵坛效果展示:血条样式进度条直观呈现各标签置信度差异

万象视界灵坛效果展示&#xff1a;血条样式进度条直观呈现各标签置信度差异 1. 平台概览 万象视界灵坛是一款基于OpenAI CLIP技术的高级多模态智能感知平台。它通过创新的像素风格界面&#xff0c;将复杂的视觉识别任务转化为直观的交互体验。平台采用16-Bit游戏美学设计&…...

YimMenu:GTA5游戏体验增强工具全攻略

YimMenu&#xff1a;GTA5游戏体验增强工具全攻略 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenu 核心痛点…...

新手入门指南:在快马平台用万文通思路打造你的第一个文本转换网页

今天想和大家分享一个特别适合编程新手的实践项目——用万文通思路在InsCode(快马)平台快速搭建文本转换网页。这个项目完全不需要复杂的环境配置&#xff0c;打开浏览器就能完成&#xff0c;特别适合想体验完整开发流程的初学者。 项目核心功能设计 这个网页的核心功能非常简单…...

3步打造Windows桌面美学:TranslucentTB让任务栏焕发新生

3步打造Windows桌面美学&#xff1a;TranslucentTB让任务栏焕发新生 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB 一、为什么你的任务栏…...

mxbai-embed-large-v1效果展示:超越OpenAI的文本嵌入模型实测

mxbai-embed-large-v1效果展示&#xff1a;超越OpenAI的文本嵌入模型实测 1. 引言&#xff1a;文本嵌入技术的新标杆 在自然语言处理领域&#xff0c;文本嵌入模型正成为各类智能应用的基础设施。mxbai-embed-large-v1作为最新开源的文本嵌入模型&#xff0c;在MTEB基准测试中…...

RobotStudio机器人轨迹规划:从工件坐标到流畅路径的实战指南

1. 工件坐标系的创建与校准 在RobotStudio中规划机器人轨迹的第一步&#xff0c;就是建立准确的工件坐标系。这就像盖房子前要先打好地基&#xff0c;坐标系就是机器人运动的"地基"。我见过不少新手直接开始示教点位&#xff0c;结果发现机器人总是跑偏&#xff0c;就…...

从‘torch not found’到成功训练:一个YOLOv8环境配置的完整避坑实录(含CUDA/cuDNN版本选择)

YOLOv8环境配置终极指南&#xff1a;从版本匹配到显存优化的全流程实战 在计算机视觉领域&#xff0c;YOLOv8作为目标检测的标杆算法&#xff0c;其安装配置过程却常常成为开发者的"拦路虎"。本文将带你系统解决从PyTorch版本选择、CUDA环境配置到显存优化的全链路问…...