设计模式08-行为型模式1(命令模式/迭代器模式/观察者模式/Java)

五、行为型模式

  • **行为模式的定义:**行为型模式是对不同的对象之间划分职责和算法的抽象化。行为型模式定义了系统中对象之间的交互与通信,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责,包括对系统中较为复杂的流程的控制。

  • 行为模式的分类:

    类行为型模式:使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责;

    对象行为型模式:使用对象的聚合关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责。

5.1 命令模式

5.1.1 命令模式的定义

描述对象之间的调用关系

**1.模式动机:**将请求发送者和接收者完全解耦;发送者与接收者之间没有直接引用关系;发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求

**2.模式定义:**将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

5.1.2 命令模式的结构与分析

image-20241031200821297

//调用者
public class Invoker {
    private AbstractCommand ac;
    public Invoker(AbstractCommand ac) {
        this.ac = ac;
    }
    public void send() {
        ac.execute();
    }
}
//接收者
public class Receiver {
    public void action() {
    }
}
//抽象命令类
public abstract class AbstractCommand {
    public abstract void execute();
}
//具体命令类
public class ConcreteCommand extends AbstractCommand {
    private Receiver receiver = new Receiver();
    @Override
    public void execute() {
        receiver.action();
    }
}
  • 命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开

  • 将请求发送者和接收者完全解耦,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。

5.1.3 命令模式的案例

电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。

image-20241031203956626

public class TV {
    public void open() {
        System.out.println("打开电视");
    }

    public void change() {
        System.out.println("切换频道");
    }

    public void close() {
        System.out.println("关掉电视");
    }
}

public interface Command {
    public void execute();
}

public class OpenCommand implements Command{
    private TV tv = new TV();

    @Override
    public void execute() {
        tv.open();
    }
}

public class CloseCommand implements Command{
    private TV tv = new TV();
    @Override
    public void execute() {
        tv.close();
    }
}

public class ChangeCommand implements Command {
    private TV tv = new TV();
    @Override
    public void execute() {
        tv.change();
    }
}

public class Control {
    private Command openCommand;
    private Command changeCommand;
    private Command closeCommand;

    public Control(Command openCommand, Command changeCommand, Command closeCommand) {
        this.openCommand = openCommand;
        this.changeCommand = changeCommand;
        this.closeCommand = closeCommand;
    }

    public void open() {
        openCommand.execute();
    }

    public void change() {
        changeCommand.execute();
    }

    public void close() {
        closeCommand.execute();
    }
}

public class Main {
    public static void main(String[] args) {
        Command openCommand = new OpenCommand();
        Command changeCommandCommand = new ChangeCommand();
        Command closeCommand = new CloseCommand();
        Control control = new Control(openCommand, changeCommandCommand, closeCommand);
        control.open();
        control.change();
        control.close();
    }
}
5.1.4 命令模式的优缺点
优点缺点
1.将系统的请求调用者和请求接收者解耦1.使用命令模式可能会导致某些系统有过多的具体命令类
2.添加命令符合开闭原则
3.可以比较容易地设计一个命令队列或宏命令(组合模式)
4.为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案
5.1.5 命令模式的适用场景
  • 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互

  • 需要在不同的时间指定请求、将请求排队和执行请求

  • 需要支持命令的撤销(Undo)操作和恢复(Redo)操作

  • 需要将一组操作组合在一起形成宏命令

5.2 迭代器模式

5.2.1 迭代器模式的定义

1.模式动机:如何访问一个聚合对象(用于存储多个对象)中的元素但又不需要暴露它的内部结构,还能提供多种不同的遍历方式

2.模式定义:提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示。

5.2.2 迭代器模式的结构与分析

image-20241102214037189

  • 聚合对象的两个职责:存储数据,聚合对象的基本职责;遍历数据,既是可变化的,又是可分离的

  • 将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中,由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则

  • 迭代器对象使用了工厂模式的设计思想,聚合类可以看作工厂模式,迭代器是产品类

实现方法一:由于集合和数组在聚合类中,迭代器需要使用它们,因此迭代器作为内部类出现

public interface Iterator {
    public void first();
    public void next();
    public boolean isLast();
    public Object currentItem();
}

public abstract class Aggregate {
    public abstract Iterator createIterator();
}

public class ConcreteAggregate extends Aggregate{
    private String[] strings;
    public ConcreteAggregate() {
        strings = new String[]{"d", "e", "f"};
    }
    @Override
    public Iterator createIterator() {
        return new ConcreteIterator();
    }

    private class ConcreteIterator implements Iterator{
        private int index = 0;

        @Override
        public void first() {
            index = 0;
        }

        @Override
        public void next() {
            if (strings.length > index) {
                index++;
            } else {
                throw new RuntimeException("越界");
            }
        }

        @Override
        public boolean isLast() {
            return index == strings.length;
        }

        @Override
        public Object currentItem() {
            return strings[index];
        }
    }
}

实现方法二:可以在聚合类创建迭代器时,将自身传给迭代器并提供集合/数组的get方法,这样也可以实现

public interface Iterator {
    public void first();
    public void next();
    public boolean isLast();
    public Object currentItem();
}

public abstract class Aggregate {
    public abstract Iterator createIterator();
}

public class ConcreteAggregate extends Aggregate{
    private String[] strings;
    public ConcreteAggregate() {
        strings = new String[]{"a", "b", "c"};
    }

    public String[] getStrings() {
        return strings;
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }
}

public class ConcreteIterator implements Iterator{
    private ConcreteAggregate aggregate;
    private int index = 0;
    public ConcreteIterator(ConcreteAggregate aggregate) {
        this.aggregate = aggregate;
    }
    @Override
    public void first() {
        index = 0;
    }

    @Override
    public void next() {
        if (aggregate.getStrings().length > index) {
            index++;
        } else {
            throw new RuntimeException("越界");
        }
    }

    @Override
    public boolean isLast() {
        return index == aggregate.getStrings().length;
    }

    @Override
    public Object currentItem() {
        return aggregate.getStrings()[index];
    }
}

public class Main {
    public static void main(String[] args) {
        Aggregate concreteAggregate = new ConcreteAggregate();
        Iterator iterator = concreteAggregate.createIterator();
        while (!iterator.isLast()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}
5.2.3 迭代器模式的案例

电视机遥控器就是一个迭代器的实例,通过它可以实现对电视机频道集合的遍历操作,本实例我们将模拟电视机遥控器的实现。

image-20241102215617007

  • 迭代器类
public interface TVIterator {
    public void setChannel(int i);
    public Object currentChannel();
    public void next();
    public void previous();
    public boolean isLast();
    public boolean isFirst();
}

public class SkyworthIterator implements TVIterator {
    private SkyworthTelevision skyworthTelevision;
    private int index = 0;

    public SkyworthIterator(SkyworthTelevision skyworthTelevision) {
        this.skyworthTelevision = skyworthTelevision;
    }

    @Override
    public void setChannel(int i) {
        this.index = i;
    }

    @Override
    public Object currentChannel() {
        return skyworthTelevision.getObj()[this.index];
    }

    @Override
    public void next() {
        if (this.index < skyworthTelevision.getObj().length) {
            this.index++;
        }
    }

    @Override
    public void previous() {
        if (this.index > 0) {
            this.index--;
        }
    }

    @Override
    public boolean isLast() {
        return this.index == skyworthTelevision.getObj().length ;
    }

    @Override
    public boolean isFirst() {
        return this.index == 0;
    }
}

public class TCLIterator implements TVIterator{
    private TCLTelevision tclTelevision;
    private int index = 0;

    public TCLIterator(TCLTelevision tclTelevision) {
        this.tclTelevision = tclTelevision;
    }

    @Override
    public void setChannel(int i) {
        this.index = i;
    }

    @Override
    public Object currentChannel() {
        return tclTelevision.getObj()[this.index];
    }

    @Override
    public void next() {
        if (this.index < tclTelevision.getObj().length) {
            this.index++;
        }
    }

    @Override
    public void previous() {
        if (this.index > 0) {
            this.index--;
        }
    }

    @Override
    public boolean isLast() {
        return this.index == tclTelevision.getObj().length;
    }

    @Override
    public boolean isFirst() {
        return this.index == 0;
    }
}
  • 电视类
public interface Television {
    public TVIterator createIterator();

    public Object[] getObj();
}

public class SkyworthTelevision implements Television{
    private Object[] obj = {"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};
    @Override
    public TVIterator createIterator() {
        return new SkyworthIterator(this);
    }

    @Override
    public Object[] getObj() {
        return obj;
    }
}

public class TCLTelevision implements Television{
    private Object[] obj = {"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};
    @Override
    public TVIterator createIterator() {
        return new TCLIterator(this);
    }

    @Override
    public Object[] getObj() {
        return obj;
    }
}
  • 客户端
import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

public class XMLUtil {
    public static Object getBean() {
        try {
            //获取XML
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document parse = db.parse(new File("src/main/java/com/tyut/text3/example1/tv.xml"));
            String tv = parse.getElementsByTagName("tv").item(0).getFirstChild().getNodeValue();

            //获取对象
            Class<?> aClass = Class.forName(tv);
            return aClass.newInstance();
        } catch (Exception e) {
            return new RuntimeException(e);
        }
    }
}

<?xml version="1.0" encoding="UTF-8" ?>
<tv>com.tyut.text3.example1.TCLTelevision</tv>
    
public class Main {
    public static void main(String[] args) {
        TCLTelevision bean = (TCLTelevision) XMLUtil.getBean();
        TVIterator iterator = bean.createIterator();
        //正序遍历
        System.out.println("正序播放电视节目:");
        while (!iterator.isLast()) {
            System.out.println(iterator.currentChannel());
            iterator.next();
        }

        //倒序遍历
        System.out.println("倒序播放电视节目:");
        iterator.setChannel(8);
        while (!iterator.isFirst()) {
            iterator.previous();
            System.out.println(iterator.currentChannel());
        }
    }
}
  • 如果需要增加一个新的具体聚合类,只需增加一个新的聚合子类和一个新的具体迭代器类即可,原有类库代码无须修改,符合开闭原则

  • 如果需要更换一个迭代器,只需要增加一个新的具体迭代器类作为抽象迭代器类的子类,重新实现遍历方法即可,原有迭代器代码无须修改,也符合开闭原则

5.2.4 迭代器模式的优缺点
优点缺点
1.支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式,并且可以对同一个聚合对象同时多次遍历1.类的个数成对增加,这在一定程度上增加了系统的复杂性
2.简化了聚合类,符合单一职责原则
3.易扩展,符合开闭原则
5.2.5 迭代器模式的适用场景
  • 访问一个聚合对象的内容而无须暴露它的内部表示

  • 需要为一个聚合对象提供多种遍历方式

  • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口

5.3 观察者模式

5.3.1 观察者模式的定义

描述对象之间的依赖关系

1.模式动机:一个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变。定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其他对象,发生改变的对象称为观察目标,被通知的对象称为观察者。

2.模式定义:定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

5.3.2 观察者模式的结构与分析

image-20241103100453988

image-20241103100725809

  • 有时候在具体观察者类ConcreteObserver中需要使用到具体目标类ConcreteSubject中的状态(属性),会存在关联或依赖关系

  • 如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违背了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响

//观察者
public interface Observer {
    public void update();
}
public class ConcreteObserver implements Observer{
    @Override
    public void update() {
        System.out.println("更新操作");
    }
}
//目标
public abstract class Subject {
    protected ArrayList<Observer> arrayList = new ArrayList<>();
    public void attach(Observer observer) {
        arrayList.add(observer);
    }

    public void detach(Observer observer) {
        arrayList.remove(observer);
    }
    public abstract void notice();
}
public class ConcreteSubject extends Subject{
    @Override
    public void notice() {
        for (Observer observer : arrayList) {
            observer.update();
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Observer concreteObserver1 = new ConcreteObserver();
        Observer concreteObserver2 = new ConcreteObserver();

        ConcreteSubject concreteSubject = new ConcreteSubject();
        concreteSubject.attach(concreteObserver1);
        concreteSubject.attach(concreteObserver2);

        concreteSubject.notice();
    }
}
5.3.3 观察者模式的案例

在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。

image-20241103105850293

public abstract class Observer {
    private String name;
    private int state;
    public Observer(String name) {
        this.name = name;
        this.state = 0;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public abstract void help();
    public abstract void hurt(AbstractTeam team);
}
public class Player extends Observer{
    public Player(String name) {
        super(name);
    }

    @Override
    public void help() {
        if (getState() == 0) System.out.println(getName() + "帮助受伤的队友");
    }

    @Override
    public void hurt(AbstractTeam team) {
        setState(1);
        System.out.println(getName() + "受伤了呼叫" + team.getTeamName() + "队友的帮忙");
        team.notice();
    }
}
public abstract class AbstractTeam {
    private String teamName;
    protected ArrayList<Observer> observers = new ArrayList<>();
    public void add(Observer observer) {
        observers.add(observer);
    }
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    public String getTeamName() {
        return teamName;
    }

    public void setTeamName(String teamName) {
        this.teamName = teamName;
    }

    public abstract void notice();
}
public class Team extends AbstractTeam {
    @Override
    public void notice() {
        for (Observer observer : observers) {
            observer.help();
        }
    }
}
public class Main {
    public static void main(String[] args) {
        AbstractTeam team = new Team();
        team.setTeamName("战狼队");
        Player player1 = new Player("张三");
        Player player2 = new Player("李四");
        Player player3 = new Player("王五");
        Player player4 = new Player("赵六");
        team.add(player1);
        team.add(player2);
        team.add(player3);
        team.add(player4);

        player1.hurt(team);
    }
}
5.3.4 观察者模式的优缺点
优点缺点
1.可以实现表示层和数据逻辑层的分离1.将所有的观察者都通知到会花费很多时间
2.在观察目标和观察者之间建立一个抽象的耦合,符合开闭原则,增加新的具体观察者无须修改原有系统代码2.如果存在循环依赖时可能导致系统崩溃
3.ü支持广播通信,简化了一对多系统设计的难度3.没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化
5.3.5 观察者模式的适用场景
  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用

  • 一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁

  • 需要在系统中创建一个触发链,A对象影响B对象,B对象影响C对象。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

边缘计算网关在机床数据采集中的应用-天拓四方

随着工业4.0和智能制造的快速发展&#xff0c;机床作为制造业的核心设备&#xff0c;其数据采集与分析对于提升生产效率、保证产品质量、优化加工过程具有重要意义。传统的数据采集方式存在数据传输速度慢、实时性差、数据处理能力有限等问题。为了解决这些问题&#xff0c;边缘…

Navicat 连接远程腾讯云服务器的MySQL数据库

首先需要开放开放腾讯云安全端口&#xff0c;可以参考这个链接腾讯云服务器入站规则端口开放使用指南(CentOS系统)。 但是注意需要开放的是IPv6&#xff0c;这个可以通过netstat命令查看确认。 然后查看当前用户信息 select user, host from mysql.user一般看到的都是 localh…

Navicat for MySQL 错误:1251

mySql&#xff1a;8.4 Navicat for MySQL&#xff1a;11.0.10 企业版 绿色版 官网中关于mysql_native_password插件的说法&#xff1a;链接 1. 问题 连接数据库报错&#xff1a;1251 要求升级Navicat for MySQL 2. 原因 mysql中的mysql_native_password插件默认是关闭的 …

mysql5安装

1.下载安装包 https://downloads.mysql.com/archives/community/ mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar tar -xvf mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar2.安装依赖 yum -y install perl yum -y install net-tools yum install numactl libaio libaio-devel -y也可…

【MyBatis】【基于轻量型架构的WEB开发】课程 课后习题 章节测试

mybatis关联查询、缓存、注解 一. 单选题 1. 下列关于 <collection> 元素的描述正确的是&#xff08;&#xff09;。 A. MyBatis 就是通过 <collection> 元素来处理一对多关联关系的 B. <collection> 元素的属性与 <association> 元素完全相同 C.…

C++ | Leetcode C++题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; class Solution { private:mt19937 gen;uniform_int_distribution<int> dis;vector<int> pre;public:Solution(vector<int>& w): gen(random_device{}()), dis(1, accumulate(w.begin(), w.end(), 0)) {partial_sum(…

opencv - py_imgproc - py_canny Canny边缘检测

文章目录 Canny 边缘检测目标理论OpenCV 中的 Canny 边缘检测其他资源 Canny 边缘检测 目标 在本章中&#xff0c;我们将学习 Canny 边缘检测的概念用于该目的的 OpenCV 函数&#xff1a;cv.Canny() 理论 Canny 边缘检测是一种流行的边缘检测算法。它由 John F. Canny 于1…

java、excel表格合并、指定单元格查找、合并文件夹

#创作灵感# 公司需求 记录工作内容 后端&#xff1a;JAVA、Solon、easyExcel、FastJson2 前端&#xff1a;vue2.js、js、HTML 模式1&#xff1a;合并文件夹 * 现有很多文件夹 想合并全部全部的文件夹的文件到一个文件夹内 * 每个部门发布的表格 合并全部的表格为方便操作 模…

数据结构作业day5

链栈&#xff0c;自己实现一遍&#xff0c;但是节点存储不是整数&#xff0c;存储学生信息&#xff08;年龄&#xff0c;分数&#xff0c;姓名&#xff09;三级引用。 1、建立学生信息结构体&#xff0c;将data改为学生信息结构体类型。 2、循环入栈和入队。 循环入栈代码 …

【学术精选】SCI期刊《Electronics》特刊“New Challenges in Remote Sensing Image Processing“

英文名称&#xff1a;New Challenges in Remote Sensing Image Processing 中文名称&#xff1a;"遥感图像处理的新挑战"特刊 期刊介绍 “New Challenges in Remote Sensing Image Processing”特刊隶属于《Electronics》期刊&#xff0c;聚焦遥感图像处理领域快速…

2024 CSS保姆级教程 - BFC详解

前言 - CSS中的文档流 在介绍BFC之前&#xff0c;需要先给大家介绍一下文档流。​ 我们常说的文档流其实分为定位流、浮动流、普通流三种。​ ​ 1. 绝对定位(Absolute positioning)​ 如果元素的属性 position 为 absolute 或 fixed&#xff0c;它就是一个绝对定位元素。​ 在…

数字化装配助力柔性制造与快速换型,驱动效率飞跃

数字化装配是利用先进的数字化技术&#xff0c;如三维建模、仿真分析、物联网、大数据、人工智能等&#xff0c;对装配过程进行精确设计、优化控制和智能管理的一种现代化生产方式。它打破传统装配依赖于人工经验和物理样机的局限&#xff0c;通过模拟环境进行预装配验证&#…

如何在服务器端对PDF和图像进行OCR处理

介绍 今天我想和大家分享一个我在研究技术资料时发现的很好玩的东西——Tesseract。这不仅仅是一个普通的库&#xff0c;而是一个用C语言编写的OCR神器&#xff0c;能够识别一大堆不同国家的语言。我一直在寻找能够处理各种文档的工具&#xff0c;而Tesseract就像是给了我一把…

QT——TCP网络调试助手

目录 一.项目展示 ​编辑 二.开发流程 三.QTcpServer、QTcpSocket、QUdpSocket类的学习 1.QTcpServer服务端 2.QTcpSocket客户端 3.Udp通信 四.网络调试助手 1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换 2.实现打开/关闭按键图片的切换 方式一&…

eclipse下载与安装(汉化教程)超详细

目录 一、下载eclipse安装包 三、配置eclipse 代码自动补全功能 安装汉化包 中英文切换 四、用eclipse写hello world 一、下载eclipse安装包 1、首先进入 eclipse官网 如下&#xff1a; 2、这里面有很多版本&#xff1b;我们小白一般选择第二个&#xff0c;向下滑动&…

[FE] React 初窥门径(四):React 组件的加载过程(render 阶段)

1. 回顾 前几篇文章中&#xff0c;我们采用了 VSCode 插件 CodeTour 来记录代码的执行过程&#xff0c; 并把相关的数据 .tour/ 放到了 github: thzt/react-tour 中。 截止到本文为之&#xff0c;我们总共记录了这些 code-tour&#xff0c; .tour/ ├── 2. 构建过程.tour ├─…

什么是数字签名技术?

信息安全五要素 名称说明机密性机密性是指网络信息不泄露给非授权的用户、实体或程序&#xff0c;能够防止非授权者获取信息完整性完整性是指网络信息或系统未经授权不能进行更改的特性可用性可用性是指合法许可的用户能够及时获取网络信息或服务的特性可控性可控性是指可以控…

人工智能原理实验一:知识的表示与推理实验

一、实验目的 本实验课程是计算机、智能、物联网等专业学生的一门专业课程&#xff0c;通过实验&#xff0c;帮助学生更好地掌握人工智能相关概念、技术、原理、应用等&#xff1b;通过实验提高学生编写实验报告、总结实验结果的能力&#xff1b;使学生对智能程序、智能算法等有…

虚拟机 Email 恢复专用工具:Virtual Machine Email Recovery

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份解决方案与服务&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 天津鸿萌科贸发展有限公司是 SysTools 系列数据恢复、取证及…

HTML 基础标签——表单标签<form>

文章目录 1. `<form>` 标签:定义表单容器2. `<input>` 标签:多用途输入控件3. `<textarea>` 标签:多行文本输入框4. `<select>` 标签:下拉选择框5. `<option>` 标签:下拉菜单选项6. `<button>` 标签:按钮元素7. `<label>` 标签…