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

设计模式の命令访问者迭代器模式

文章目录

  • 前言
  • 一、命令模式
  • 二、访问者模式
  • 三、迭代器模式


前言

  本篇是关于设计模式中命令模式、访问者模式、以及迭代器模式的学习笔记。


一、命令模式

  命令模式是一种行为型设计模式,其核心目的在于将命令的发送者和接受者解耦,提供一个中间层对命令进行统一的管理,请求的发送者不需要知道接收者的具体实现,而只需依赖一个抽象的命令接口。
  命令模式通常包括以下的角色:

  • 抽象/接口命令层:定义了执行命令的抽象方法,例如执行,撤销。
  • 具体命令类:继承/实现了命令层,对命令的具体内容做一个描述。
  • 接收者:实际执行操作的对象,包含与请求相关的逻辑。
  • 调用者:调用命令对象以触发请求的执行。
  • 客户端:创建具体的命令对象,并将其绑定到调用者上。

  举一个生活中的案例,假设对于家电,想要通过一个万能遥控器去控制某个家电的开/关,而不需要每个家电都配一个遥控器,则可以将开,关设为具体命令类:

/*** 命令抽象类*/
public interface Command {/*** 执行命令*/void execute();/*** 回滚操作*/void undo();
}

  以对灯的开关操作为例,开灯的命令类:

/*** 开灯*/
public class LightOnCommand implements Command{private Light light;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.on();}@Overridepublic void undo() {light.off();}
}

  关灯的命令类:

/*** 关灯*/
public class LightOffCommand implements Command{private Light light;public LightOffCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.off();}@Overridepublic void undo() {light.on();}
}

  具体的电器,有开关的操作。

public class Light {public void on(){System.out.println("把灯打开");}public void off(){System.out.println("关灯了");}
}

  以及一个空的命令类

public class NoCommand implements Command{/*** 执行命令*/@Overridepublic void execute() {}/*** 回滚操作*/@Overridepublic void undo() {}
}

  命令的统一管理类,主要作用:

  • 对命令组进行初始化。
  • 设置操控某一个对象的具体命令。
  • 执行具体的命令。
  • 撤销命令。
public class RemoteController {//存放各种设备的开命令Command[] onCommands;//存放各种设备的关命令Command[] offCommands;//撤销的命令Command unCommand;/*** 初始化空命令*/public RemoteController(int number) {onCommands = new Command[number];offCommands = new Command[number];for (int i = 0; i < number; i++) {onCommands[i] = new NoCommand();offCommands[i] = new NoCommand();}}/*** 设置具体的命令** @param no         设备组编号* @param onCommand  具体设备组设备的开命令* @param offCommand 具体设备组设备的关命令*/public void setCommand(int no, Command onCommand, Command offCommand) {onCommands[no] = onCommand;offCommands[no] = offCommand;}/*** 按下某个设备的开按钮** @param no 设备组编号*/public void onButtonWasPushed(int no) {onCommands[no].execute();//记录本次操作,便于撤销unCommand = onCommands[no];}/*** 按下某个设备的关按钮** @param no 设备组编号*/public void offButtonWasPushed(int no) {offCommands[no].execute();//记录本次操作,便于撤销unCommand = offCommands[no];}/*** 撤销*/public void undo() {unCommand.undo();}
}

  客户端:

public class Client {public static void main(String[] args) {Light light = new Light();//初始化开启电灯命令LightOnCommand lightOnCommand = new LightOnCommand(light);//初始化关闭电灯命令LightOffCommand lightOffCommand = new LightOffCommand(light);//初始化遥控器面板RemoteController remoteController = new RemoteController(5);remoteController.setCommand(0, lightOnCommand, lightOffCommand);System.out.println("--------开启电灯--------");remoteController.onButtonWasPushed(0);System.out.println("--------关闭电灯--------");remoteController.offButtonWasPushed(0);System.out.println("--------撤销上一步操作--------");remoteController.undo();}
}

  如果需要进行扩展,比如电视,只需要去增加一个电视机对象,以及两个实现了命令接口的具体命令类,最后在客户端进行初始化即可,无需对命令管理类进行修改。

二、访问者模式

  访问者模式是一种行为型设计模式,主要用来将操作与对象结构分离。核心思想在不修改对象结构的前提下,定义作用于这些对象的新操作。
  访问者模式通过引入一个访问者对象,将对结构中各个元素的操作从元素类中抽离出来。这样,当需要添加新操作时,无需修改元素类,而只需添加新的访问者类,访问者模式由以下几个角色组成:

  • 访问者接口:声明对每种元素类型的访问操作方法。
  • 具体访问者:实现访问者接口,定义具体的操作逻辑。
  • 元素接口:定义接受访问者对象的方法。
  • 具体元素:实现元素接口,在接受访问者对象的方法中调用访问者的相应方法。
  • 对象结构:包含一组元素,可以遍历这些元素并让访问者访问它们。

  举一个生活中的案例,例如在网上商城将商品加入购物车,需要计算商品总价,还需要获取商品的详细清单,利用访问者模式,可以将商品作为被访问的类

/*** 被访问的元素接口*/
public interface Item {void accept(Visitor visitor);
}
/*** 被访问的元素:书籍*/
public class Book implements Item {private String title;private double price;public Book(String title, double price) {this.title = title;this.price = price;}public String getTitle() {return title;}public double getPrice() {return price;}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}
}
/*** 被访问的元素:电子产品*/
public class Electronic implements Item {private String name;private double price;public Electronic(String name, double price) {this.name = name;this.price = price;}public String getName() {return name;}public double getPrice() {return price;}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}
}

  定义一个访问者接口层,访问具体的商品(书籍,电子产品):

public interface Visitor {void visit(Book book);void visit(Electronic electronic);
}

  定义计算价格和打印清单具体的行为:

/*** 打印清单*/
public class ItemPrinterVisitor implements Visitor {@Overridepublic void visit(Book book) {System.out.println("Book: " + book.getTitle() + " - Price: " + book.getPrice());}@Overridepublic void visit(Electronic electronic) {System.out.println("Electronic: " + electronic.getName() + " - Price: " + electronic.getPrice());}
}
/*** 计算书籍和电子产品的价格*/
public class PriceCalculatorVisitor implements Visitor{private double totalPrice = 0;@Overridepublic void visit(Book book) {totalPrice = totalPrice+book.getPrice();}@Overridepublic void visit(Electronic electronic) {totalPrice = totalPrice + electronic.getPrice();}public double getTotalPrice(){return totalPrice;}
}

  以及具体的对象接口,让访问者去操作书籍和电子产品:

/*** 对象接口*/
public class ShoppingCart {private List<Item> items = new ArrayList<>();public void addItem(Item item) {items.add(item);}public void accept(Visitor visitor) {for (Item item : items) {item.accept(visitor);}}
}

  客户端:

public class Client {public static void main(String[] args) {// 创建购物车ShoppingCart cart = new ShoppingCart();cart.addItem(new Book("Spring源码深度解析", 50));cart.addItem(new Electronic("Iphone17", 6300));// 计算总价PriceCalculatorVisitor priceCalculator = new PriceCalculatorVisitor();cart.accept(priceCalculator);System.out.println("Total Price: " + priceCalculator.getTotalPrice());// 打印清单ItemPrinterVisitor itemPrinter = new ItemPrinterVisitor();cart.accept(itemPrinter);}
}

  这样的好处在于,将具体的操作对象结构分离,后续增加具体的操作,无需对对象结构进行修改。因此适用于需要对对象结构中的对象执行多种不相关的操作,而操作的实现细节需要彼此独立的场景。
  同时也可以体会一下如果不使用访问者模式,会存在怎么样的弊端:

  1. 代码耦合度高:在ShoppingCart类中,我们需要判断每个商品的类型,并为不同类型的商品执行不同的操作。这导致了ShoppingCart 类与具体商品类型的紧密耦合。如果要新增一种商品?就又要加一个条件分支。
  2. 扩展性差:同样地,除了计算价格和打印清单,如果还需要再增加一种操作?那么购物车的代码还需要进行修改。
// 书籍类(Book)
public class Book {private String title;private double price;public Book(String title, double price) {this.title = title;this.price = price;}public String getTitle() {return title;}public double getPrice() {return price;}
}// 电子产品类(ElectronicProduct)
public class ElectronicProduct {private String name;private double price;public ElectronicProduct(String name, double price) {this.name = name;this.price = price;}public String getName() {return name;}public double getPrice() {return price;}
}

  在购物车中直接计算总价,打印清单:

import java.util.ArrayList;
import java.util.List;public class ShoppingCart {private List<Object> items;public ShoppingCart() {items = new ArrayList<>();}public void addItem(Object item) {items.add(item);}public List<Object> getItems() {return items;}// 计算总价public double calculateTotalPrice() {double totalPrice = 0;for (Object item : items) {if (item instanceof Book) {totalPrice += ((Book) item).getPrice();} else if (item instanceof ElectronicProduct) {totalPrice += ((ElectronicProduct) item).getPrice();}}return totalPrice;}// 打印订单清单public void printOrderDetails() {System.out.println("Shopping Cart Details:");for (Object item : items) {if (item instanceof Book) {Book book = (Book) item;System.out.println("Book: " + book.getTitle() + ", Price: " + book.getPrice());} else if (item instanceof ElectronicProduct) {ElectronicProduct product = (ElectronicProduct) item;System.out.println("Product: " + product.getName() + ", Price: " + product.getPrice());}}}
}

三、迭代器模式

  迭代器模式是一种行为型设计模式,核心思想是将集合的遍历操作与集合的实现分离,从而不暴露集合的内部结构,只需要依赖一个公共的接口来访问集合中的元素。通过这种方式,可以在不改变集合结构的情况下,提供不同的遍历方式和逻辑,通常包含以下的角色:

  • 迭代器接口:定义遍历集合元素的方法,可以使用JDK的Iterator接口。
  • 具体迭代器:实现迭代器接口,负责具体的遍历逻辑。
  • 聚合接口:定义返回一个迭代器的方法。
  • 具体聚合:实现聚合接口,维护集合的实际数据结构,并提供创建迭代器的方法。

  假设现在有一个电商项目,需要管理多个订单。每个订单由多个商品组成。希望能够顺序遍历订单中的商品,而不暴露订单内部的实现细节,首先创建一个商品的实例

public class Product {private String productName;private double price;public Product(String productName, double price) {this.productName = productName;this.price = price;}public String getProductName() {return productName;}public double getPrice() {return price;}
}

  再编写一个聚合接口

public interface Aggregate {Iterator createIterator();
}

  创建具体聚合迭代器接口

public class Order implements Aggregate{private String orderId;private Product[] items;public Order(String orderId, Product[] items) {this.orderId = orderId;this.items = items;}public String getOrderId() {return orderId;}public Product[] getItems() {return items;}@Overridepublic Iterator createIterator() {return new OrderIterator(this);}
}
public class OrderIterator implements Iterator {private Order order;private int index;public OrderIterator(Order order) {this.order = order;this.index = 0;}@Overridepublic boolean hasNext() {return index < order.getItems().length;}@Overridepublic Object next() {if (hasNext()) {return order.getItems()[index++];}return null;}
}

  客户端,创建产品并且遍历:

public class Client {public static void main(String[] args) {Product p1 = new Product("产品1", 5);Product p2 = new Product("产品2", 6);Product p3 = new Product("产品3", 7);Order order = new Order("10001", new Product[]{p1, p2, p3});Iterator iterator = order.createIterator();while (iterator.hasNext()){Product next = (Product) iterator.next();System.out.println(next);}}
}

  同时也可以对比一下不使用迭代器模式,在客户端中直接遍历,体会一下存在哪些弊端:

  1. 客户端直接操作 Order 类的内部实现,如果以后把商品的存储方式改为其他数据结构,所有使用 Order 类的代码都需要修改。
  2. 遍历商品数组的逻辑直接写在了客户端,每个需要遍历商品的地方,都必须重复写遍历逻辑。
  3. 以及如果需要进行不同方式的遍历,需要修改遍历逻辑以及所有使用到的客户端的代码。
public class Product {private String productName;private double price;public Product(String productName, double price) {this.productName = productName;this.price = price;}public String getProductName() {return productName;}public double getPrice() {return price;}
}public class Order {private String orderId;private Product[] items;public Order(String orderId, Product[] items) {this.orderId = orderId;this.items = items;}public String getOrderId() {return orderId;}public Product[] getItems() {return items;}
}public class Client {public static void main(String[] args) {// 创建产品Product p1 = new Product("Laptop", 1000);Product p2 = new Product("Smartphone", 800);Product p3 = new Product("Headphones", 150);// 创建订单Product[] products = {p1, p2, p3};Order order = new Order("O1001", products);// 直接在客户端遍历商品Product[] items = order.getItems();for (int i = 0; i < items.length; i++) {System.out.println("Product: " + items[i].getProductName() + ", Price: " + items[i].getPrice());}}
}

相关文章:

设计模式の命令访问者迭代器模式

文章目录 前言一、命令模式二、访问者模式三、迭代器模式 前言 本篇是关于设计模式中命令模式、访问者模式、以及迭代器模式的学习笔记。 一、命令模式 命令模式是一种行为型设计模式&#xff0c;其核心目的在于将命令的发送者和接受者解耦&#xff0c;提供一个中间层对命令进行…...

信息系统项目管理 -冲突管理

信息系统项目管理题 冲突管理&#xff1a; 项目管理信息系统包括&#xff08;&#xff09;软件&#xff0c;用于监督资源的使用情况&#xff0c;协助确保合适的资源适时、适地的用于合适活动。 A资源管理或进度计划 BCRM系统 C采购系统或智能分析 DBOM系统 答案&#xff1a;A …...

Gmsh有限元网格剖分(Python)---点、直线、平面的移动

Gmsh有限元网格剖分(Python)—点、直线、平面的移动和旋转 最近在学习有限元的网格剖分算法&#xff0c;主要还是要参考老外的开源Gmsh库进行&#xff0c;写一些博客记录下学习过程&#xff0c;方便以后回忆嘞。 Gmsh的官方英文文档可以参考&#xff1a;gmsh.pdf 但咋就说&a…...

山景BP1048增加AT指令,实现单片机串口控制播放音乐(一)

1、设计目的 山景提供的SDK是蓝牙音箱demo&#xff0c;用户使用ADC按键或者IR遥控器&#xff0c;进行人机交互。然而现实很多场景&#xff0c;需要和单片机通信&#xff0c;不管是ADC按键或者IR接口都不适合和单片机通信。这里设计个AT指令用来和BP1048通信。AT指令如下图所示…...

SMMU软件指南SMMU编程之全局错误和最小配置

安全之安全(security)博客目录导读 目录 一、全局错误 二、最小配置 一、全局错误 与编程接口相关的全局错误会报告到适当的 SMMU_(*_)GERROR 寄存器&#xff0c;而不是通过基于内存的事件队列。这些错误通常是严重的&#xff0c;例如导致 SMMU 停止向前推进。例如&#xf…...

CPU条件下Pytorch、jupyter环境配置

一、创建虚拟环境 查看虚拟环境 conda env list 创建python虚拟环境 conda create -n minist python3.11 激活虚拟环境 conda activate minist 查看虚拟环境下有哪些包 pip list 二、安装pytorch 切换清华源 conda config --add channels https://mirrors.tuna.tsing…...

【自用】通信内网部署rzgxxt项目_01,后端pipeDemo部署(使用nssm.exe仿照nohup)

做完这些工作之后&#xff0c;不要忘记打开 Windows Server 的防火墙端口&#xff0c;8181、8081、8080、22、443、1521 做完这些工作之后&#xff0c;不要忘记打开 Windows Server 的防火墙端口&#xff0c;8181、8081、8080、22、443、1521 做完这些工作之后&#xff0c;不要…...

Ubuntu 安装实时内核指南

在运行需要高精度和低延迟响应的机器人驱动程序时&#xff0c;安装一个具备实时内核&#xff08;Real-Time Kernel&#xff09;的 Ubuntu 系统是至关重要的。若缺乏实时系统的支持&#xff0c;高频率的控制指令可能会导致机器人运动轨迹不流畅&#xff0c;甚至产生抖动现象。以…...

MySQL 主从复制与高可用

在现代分布式系统中&#xff0c;数据库的高可用性和可靠性至关重要。MySQL 提供了主从复制&#xff08;Master-Slave Replication&#xff09;机制来实现数据的冗余和容错&#xff0c;保证在主数据库发生故障时能够继续提供服务。而在此基础上&#xff0c;通过进一步的高可用架…...

RCE总结

文章目录 常见漏洞执行函数&#xff1a;1.系统命令执行函数2.代码执行函数 命令拼接符读取文件命令绕过&#xff1a;空格过滤绕过关键字绕过长度过滤绕过无参数命令执行绕过无字母数字绕过利用%0A截断利用回溯绕过利用create_function()代码注入无回显RCE1.反弹shell2.dnslog外…...

基于UNITY3D的照片墙演示项目技术分享

unity实现超大图片墙演示,由于拥有海量图片&#xff0c;使用了CPU 多线程&#xff0c;unity dots技术&#xff0c;图片组成文字部分&#xff0c;使用了点阵图技术&#xff0c;提取文字像素。 &#xff08;关于点阵介绍&#xff09; 点阵字体是把每一个字符都分成1616或2424个点…...

随手记:小程序兼容后台的wangEditor富文本配置链接

场景&#xff1a; 在后台配置wangEditor富文本&#xff0c;可以文字配置链接&#xff0c;图片配置链接&#xff0c;产生的json格式为&#xff1a; 例子&#xff1a; <h1><a href"https://uniapp.dcloud.net.cn/" target"_blank"><span sty…...

maven项目运行时NoSuchMethodError问题排查记录(依赖冲突解决)

控制台异常如下&#xff1a; Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: org.apache.commons.io.input.BoundedInputStream.builder()Lorg/apache/commons/io/input/BoundedInputStream$Builder;问题明显&#xff0c;根据NoSuchMethodError…...

ECharts关系图-关系图11,附视频讲解与代码下载

引言&#xff1a; 关系图&#xff08;或称网络图、关系网络图&#xff09;在数据可视化中扮演着至关重要的角色。它们通过节点&#xff08;代表实体&#xff0c;如人、物体、概念等&#xff09;和边&#xff08;代表实体之间的关系或连接&#xff09;的形式&#xff0c;直观地…...

【C语言】动态内存管理:详解malloc和free函数

前言 在C语言编程中&#xff0c;动态内存分配是一个非常重要的概念。与静态内存分配不同&#xff0c;动态内存分配允许程序在运行时根据需要分配和释放内存&#xff0c;从而更加灵活地管理内存资源。特别是在一些数据结构的引用中经常需要使用&#xff0c;下面我们就详细讲解一…...

EGO Swarm翻译

目录 摘要 Ⅰ 介绍 Ⅱ 相关工作 A . 单四旋翼局部规划 B . 拓扑规划 C. 分布式无人机集群 Ⅲ 基于梯度的局部规划隐式拓扑轨迹生成 A.无需ESDF梯度的局部路径规划 B.隐式拓扑轨迹生成 Ⅳ 无人机集群导航 A 机间避碰 B. 定位漂移补偿 C. 从深度图像中去除agent Ⅴ …...

Linux根目录

在Linux系统中&#xff0c;文件系统遵循一种标准化的目录结构&#xff0c;即文件系统层次结构标准&#xff08;Filesystem Hierarchy Standard&#xff0c;FHS&#xff09;。 根目录&#xff08;/&#xff09; /bin&#xff1a;包含二进制可执行文件&#xff0c;通常是用户和系…...

SAP-SD-参照退货订单补货时带不出行项目

业务场景&#xff1a; 当物料出现质量问题时&#xff0c;客户需要换货&#xff0c;不需要退款&#xff0c;就需要先做退货订单&#xff0c;然后参照退货订单进行补货&#xff0c;创建补货订单&#xff0c;但是创建补货订单时只是把抬头数据带入补货订单&#xff0c;没有带入行项…...

12-C语言单向链表

一、链表的概述 1.链表与数组对比 遍历数组中的数据&#xff0c;查询数据比较方便&#xff0c;但往数组中插入、删除数据需要移动大量数据&#xff1b;相反。链表遍历、查询数据不方便&#xff0c;但是插入、删除数据比较方便&#xff0c;不需要移动大量数据&#xff0c;直接…...

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题&#xff08;选择题&#xff09; 题目总数&#xff1a;5 总分数&#xff1a;50 选择题 第 1 题 单选题 Scratch运行以下程宇后&#xff0c;小兔子会&#xff08; &#xff09;。 A. 变小 B. 变大 C. 变色 D. …...

FFmpeg 4.3 音视频-多路H265监控录放C++开发二十一.2,RTP协议-RTP协议概述,协议详情

前提: 为什么要学习 RTP&#xff08;Real-time Transport Protocol&#xff09;重点 简介&#xff1a;RTP是一个实时传输媒体数据的协议&#xff0c;通常与RTSP一起使用。它负责在网络上传输音视频数据。特点&#xff1a;RTP通过UDP或TCP传输媒体数据&#xff0c;提供时间戳和序…...

Linux系统编程——系统内核中的信号

目录 一、前言 二、系统内核中的信号 三、sigset_t 四、信号集操作 1、sigpending(); 2、sigemptyset(); 3、sigfillset(sigset_t *set); 4、int sigaddset ()和sigdelset() ​编辑 5、sigismember() 6、sigprocmask() 五、信号集操作代码演示 六、深入理解进程的信…...

delve调试环境搭建—golang

原文地址&#xff1a;delve调试环境搭建—golang – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 由于平时不用 IDE 开发环境&#xff0c;习惯在 linux终端vim 环境下开发&#xff0c;所以找了golang的调试工具&#xff0c;delve类似gdb的调试界…...

shell脚本的循环-----while和for循环

一、while 1.格式 while 条件表达式; do 命令 done 2.案例 &#xff1a; ping测试子网段的主机网段由用户输入&#xff0c;例如用户输入192.168.101 &#xff0c;则ping192.168.101.125 — 192.101.131 UP&#xff1a; /tmp/host_up.txt Down: /tmp/host_down.txt &#…...

【游戏设计原理】21 - 解谜游戏的设计

你想象一下&#xff0c;刚坐下准备玩游戏&#xff0c;想着“今天得挑战一下我的智商极限&#xff01;”可结果碰上一个谜题&#xff0c;傻眼了&#xff0c;心里默念&#xff1a;“这啥玩意儿&#xff1f;这游戏是在玩我吗&#xff1f;”如果这个谜题太简单了&#xff0c;你可能…...

【漏洞复现】Wordpress GutenKit 插件 远程文件写入致RCE漏洞复现(CVE-2024-9234)

🏘️个人主页: 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦 一、漏洞概述 1.1漏洞简介 漏洞名称:Wordpress GutenKit 插件 远程文件写入致RCE漏洞复现漏洞编号:CVE-2024-9234漏洞威胁等级:超危影响范围:GutenKit <= 2.1.0…...

深度学习任务简介:分类、回归和生成

深度学习任务简介&#xff1a;分类、回归和生成 文章目录 深度学习任务简介&#xff1a;分类、回归和生成一、分类任务&#xff08;Classification Task&#xff09;什么是分类任务&#xff1f;**分类任务的常见应用**分类任务的输出主要算法 二、回归任务&#xff08;Regressi…...

【测试】Unittest

近期更新完毕&#xff0c;建议关注收藏&#xff01; 目录 简介TestCaseTestSuiteTestRunnerTestLoaderFixture tips:ctrl? 可以看方法的help文档 简介 python自带的单元测试框架&#xff0c;也可以做自动化测试 组织多个用例去执行&#xff0c;用例都是用单独的目录存放的 丰…...

java 根据路径下载文件转换为MultipartFile,并且上传到服务器

直接上代码 controller层 GetMapping("/downloadAndUploadAttachment")UpdateOperationLogging(msg "根据路径下载文件转换为MultipartFile,并且上传到服务器")Operation(summary "根据路径下载文件转换为MultipartFile,并且上传到服务器", de…...

Onvif服务端开发

实现了Onvif服务端的设备搜索和RTSP流的功能。用 ONVIF Device Manager 测试工具可以成功搜索到设备和获取到RTSP流&#xff0c;有的路由器可能不支持239.255.255.250组播&#xff0c;我一开始用的电信的那种光猫路由器二合一的&#xff0c;一直搜不到设备&#xff0c;后面用So…...