浅析设计模式5 -- 责任链模式

news2024/11/23 11:17:12

7947f7e3e64ddcd03fc261fcd62d75bf.gif

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。本专题着眼于实际开发过程中常用的几种设计模式,从理论和实战两个角度进行讨论和分享,力求逻辑清晰、表述简洁,帮助大家在项目中合理运用设计模式,保障代码的可靠性。

本文为此系列第五篇文章,前四篇见——

第一篇:浅析设计模式1 —— 工厂模式

第二篇:浅析设计模式2 —— 策略模式

第三篇:浅析设计模式3 —— 装饰者模式

第四篇:浅析设计模式4——模板方法模式

4a5e35713a6f464cf62a657a0135ffd1.png

概述

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度,而设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。

d1e82b46680bf86042a0a0ce2373a927.png


大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。其中,行为型模式可用于描述程序中多个类和多个对象如何协作完成复杂的任务,涉及不同对象间的职责分配、算法的抽象化。责任链模式是一种典型的行为型模式,本文将着眼于责任链模式进行学习分享,阐述责任链模式的核心概念和应用实践。认识和使用责任链模式,可以在开发中有效增强应用的灵活性和可扩展性。

d7ddb635985cc4abd47bee5c48f03513.png

a25c2e148e45268dac79b37e70ac1443.png

基本概念

责任链模式的核心思想是:将请求的每个处理者都视为一个处理节点,再将这些节点连成一条链,当请求到来后便可沿着这条链进行传递,直到有节点处理它为止。这种模式可以有效避免请求发送者和请求接受者之间的耦合关系,用户只需要将请求发送到责任链上,不用关心传递过程和处理细节。

下面从模式结构和使用步骤两个方面,简单阐述责任链方法模式的基本概念。

▐  结构

责任链模式的结构也比较简单易懂,主要包含三大类:抽象处理者类、具体处理者类和客户类,抽象处理者类中首先定义好抽象方法和后继处理机制,具体的处理方法实现将在具体处理者中执行,而客户类将对所有定义的具体处理者组装成链,让请求从链头开始沿着责任链向后执行。

角色

关系

作用

抽象处理者    Handler

具体处理者的父类

定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

具体处理者   Concrete Handler

抽象构件的接口实现类

实现抽象处理者的抽象处理方法,判断能否处理本次请求,如果可以则处理,否则将转交给其后继者。

客户类   Client

将具体处理者组装成责任链

创建处理链,并向链头的具体处理者提交请求,它不关心处理细节和请求的传递过程。

8674f6417543180fd98e60c702cdeaf8.png

4bd539fe43cae4e82d9fb84166bcacd8.png

▐  使用

基于上述基本概念,将装饰者模式的使用步骤概括为:

step1:创建抽象处理者类,定义一个抽象方法和一个指向下一处理者节点的指针 next。

step2:创建具体处理者类,实现抽象处理者类中定义的抽象方法;

step3:创建客户类,将各个具体处理者类组装成责任链,将请求提交给链头的具体处理者类。

f59bc11e3882adfee36267e518ac6b27.png

使用示例

其实,大家平常接触到的各种审批流,就涉及多个审批节点,不同的审批节点负责人分别持有不同级别的审批权限。比如:请假审批流、紧急发布审批流、报销审批流等等。疫情管控时期,我们学校为了确保在校师生及其他员工的安全,会严格要求学生不能随意出入校门,如果有特殊情况如看病就医,则需在校务系统中进行申请,系统会根据学生请假出校的时长,设置不同级别的负责人进行严格审批。这一过程,用责任链模式就可以实现。

▐  代码实现

// 创建抽象处理者类,定义指向下一节点的指针和抽象处理方法
public abstract class AbstractHandler {
    private AbstractHandler next;
    public void setNext(AbstractHandler next) {
        this.next = next;
    }
    public AbstractHandler getNext() {
        return next;
    }
    public abstract void handleRequest(int leaveDayNum);
}


// 定义具体处理者类1:系统自动审批
public class ConcreteHandler1 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 1) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 学校自动审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}


// 定义具体处理中类2:导师审批
public class ConcreteHandler2 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 3) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 导师审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}


// 定义具体处理者类3:辅导员审批
public class ConcreteHandler3 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 5) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 辅导员审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}


// 定义具体处理者类4:院长审批
public class ConcreteHandler4 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 7) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 院长审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}


//定义客户类:组装责任链,向链头节点发送请求
public class chainOfResponsibility {
    public static void main(String[] args) {
        AbstractHandler handler1 = new ConcreteHandler1();
        AbstractHandler handler2 = new ConcreteHandler2();
        AbstractHandler handler3 = new ConcreteHandler3();
        AbstractHandler handler4 = new ConcreteHandler4();
        handler1.setNext(handler2);
        handler2.setNext(handler3);
        handler3.setNext(handler4);
        handler1.handleRequest(6);
    }
}

▐  结果输出

请假不超过6天: 院长审批通过

▐  UML图

27b26ebae76632f0ca8636150dfe9916.png

4b58003516b4f0bfc9754ac7c6e08f7d.png

扩展

很多文章在介绍责任链模式时都会提到一个概念:纯的责任链模式、不纯的责任链模式,这里也做一个简单的扩展。

  1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下一个具体处理者处理。

  2. 不纯的职责链模式:允许出现某一个具体处理者在承担了请求的部分责任后,再将剩余责任传给下一个具体处理者继续处理剩余部分责任。

另外,在日常使用中,Handler 不是一定要提供一个设置后继处理器的接口,可以把维护链路的职责独立出来,如下图所示。

639f4a6280a774ae7316afd5d9a31a3a.png

在这种责任链模式中,HandlerChain 来维护整条链路,它提供了增删处理者的方法,并实现了抽象处理者类的接口,负责在责任链中传递请求 Request。同时,HandlerChain 内部维护了当前具体处理者在整个责任链中的索引。当客户类发出请求后,HandlerChain 会驱动第一个具体处理者(pos = 0),并根据需要判断是否继续向后传递,若需要则调用 handle() 方法再次驱动下一个具体处理者(pos 在上一次驱动时自增),若不需要则跳出链路;如此反复,直到有具体处理者判断不需再向后传递,或已经执行到最后一个具体处理者。

这样,每个具体处理者不再依赖后继处理器,而是各自在 HandlerChain 中的位置 pos,从而增强模式的灵活性,解除具体处理者之间的依赖性。Servlet 中的过滤器 Filter 就是基于这种方式实现的。

2777230e32e66a9b68e6ffce5fd52b2c.png

源码赏析

Servlet 约定在请求进入容器后、执行 Servlet.service() 方法前,可以通过 Filter 对 web 资源进行过滤、权限鉴别等处理。在 tomcat 启动时,先加载所有的过滤器信息;在 tomcat 收到请求时,再加载并执行整个过滤器的链路,当请求从链路中脱离后,才会进入真正的业务接口,如下图所示。

3eea5090c16738ac938eaa83e70615da.png

在 tomcat 中,每一个 Filter 都是一个具体处理者,不仅能处理请求、还能处理响应。对于 request 来说,责任链结构为 Filter1 -> Filter2 -> Filter3;而对于响应来说,责任链结构为 Filter3 -> Filter2 -> Filter1。这种双向处理的思想其实也很经典,下面将简单分析一下源码。

public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
    public default void destroy() {}
}

Filter 为抽象处理者,提供了三个方法,其中 doFilter() 方法为处理方法,三个参数分别为请求、响应和链路管理器。接下来看一下链路管理器 FilterChain 的源码。

// FilterChain
public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;
}


// ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {    
    // 处理器链路,ApplicationFilterConfig 可以理解为对 Filter 的包装
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];   
    // 当前处理器在链路中的索引
    private int pos = 0;    
    // 调用入口
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            // here
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            // here
            internalDoFilter(request,response);
        }
    }
    
    // 开始处理
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
        // 不停的驱动下一个过滤器
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();


                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();


                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    // 驱动过滤器
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }


        // 链路中没有更多过滤器了,开始进入 servlet
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }
            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                // 进入servlet 处理实际业务
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }
}

ApplicationFilterChain 用数组来组织各处理者的先后顺序,并提供一个当前处理者在链路中的索引 pos 。当链路未在中途断开且当前处理者已是最后一个处理者时,调用 Servlet.service(request, response) 进入业务处理逻辑。作为用户,我们可以通过配置文件或者注入等方式,根据需要定义新的 Filter。

5689071f15098e32b226f0310cbf8503.png

优缺点及适用场景

▐  优点

  1. 降低对象之间的耦合度。一个节点对象无须关心链的结构、到底是哪一个对象处理其请求,发送者和接收者也无须拥有对方的明确信息。

  2. 增强系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

  3. 灵活地为对象指派职责。当工作流程变化时,可动态改变节点或调动节点次序,也可动态增删节点。

  4. 简化节点之间的连接。各节点只需保持一个指向其后继者的引用,避免使用众多 if 或 if…else 语句。

  5. 责任分担,符合类的单一职责原则。每个节点类只需处理自己该处理的工作,不该处理的传递给下一个节点类完成,各类的责任范围非常明确。

▐  缺点

  1. 不能保证每个请求一定被处理。一个请求没有明确的接收者,可能一直传到链的末端都得不到处理。

  2. 当责任链太长时,一个请求可能需要涉及多个处理者,系统性能会受到一定影响。

  3. 责任链建立的合理性需要由客户端来保证,增加了客户端的复杂性,也可能会因为错误设置而导致系统陷入死循环。

▐  适用场景

  1. 在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。

  2. 不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。

  3. 需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。

  4. 职责链模式常被用在框架开发中,实现过滤器、拦截器等功能,使用者可以在不修改源码的情况下,添加新的过滤拦截功能。

a69c2ae55859b735c71d8b2c27eed7eb.png

团队介绍

我们是大聚划算技术团队。负责支持聚划算、百亿补贴、天天特卖、淘特价等业务。我们聚焦优惠和选购体验,通过数智化驱动形成更有效率和确定性的货品运营方法论,为消费者提供精选和极致性价比的商品,为商家提供更具爆发确定性的营销方案。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

【Kubernetes 架构】了解 Kubernetes 网络模型

Kubernetes 网络使您能够在 k8s 网络内配置通信。它基于扁平网络结构&#xff0c;无需在主机和容器之间映射端口。 Kubernetes 网络支持容器化组件之间的通信。这种网络模型的主要优点是不需要在主机和容器之间映射端口。然而&#xff0c;配置 Kubernetes 网络模型并不是一件容…

随机过程与排队论(四)

设有2个红球&#xff0c;4个白球&#xff0c;先将它们分放到甲、乙两个盒子中去&#xff0c;各方3个。设X为甲盒中的红球数&#xff0c;然后再在甲、乙两盒各取一个进行交换。设Y为此时甲盒中的红球数。 求X的分布律。已知X的条件下求Y的分布律。求Y的分布律。 概率空间(Ω…

springboot+vue医院网上预约挂号系统4n9w0

在线挂号平台已经成为它运营过程中至关重要的因素。医院挂号管理系统&#xff0c;是在计算机与通信设备十分完备的基础上&#xff0c;为医院管理人员、医生、用户提供的系统化的管理平台。 本系统需要实现基础的医院介绍、线上挂号、在线咨询、医生请假等几个主要功能。 管理员…

fftw3库在Android Studio中的编译和使用

fftw3库是快速傅里叶变换FFT/IFFT的开源实现&#xff0c;可以在多个平台编译。在Android app开发项目中需要做FFT信号分析&#xff0c;优先使用JNI的方式&#xff0c;使用原生语言C/C实现复杂的科学计算任务。fftw3可以在多个平台编译优化&#xff0c;也可以在Android NDK开发时…

微信小程序nodejs+vue剧本杀游戏设计与实现

开发语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发 析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了小程序的需求基础上需要进一步地…

第十二届蓝桥杯c++b组国赛题解(还在持续更新中...)

试题A&#xff1a;带宽 解题思路&#xff1a; 由于小蓝家的网络带宽是200Mbps&#xff0c;即200Mb/s&#xff0c;所以一秒钟可以下载200Mb的内容&#xff0c;根据1B8b的换算规则&#xff0c;所以200Mb200/8MB25MB。所以小蓝家的网络理论上每秒钟最多可以从网上下载25MB的内容。…

庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)>

庄懂的TA笔记&#xff08;十八&#xff09;&#xff1c;特效&#xff1a;走马灯(序列帧) 极坐标(UV转中心点) 大纲&#xff1a; 一、走马灯&#xff1a;序列帧 双通道&#xff0c;双Pass 二、极坐标&#xff1a; 三、分享&#xff1a; 正文&#xff1a; 一、走马灯&#xff1a…

H3C交换机基于MAC的VLAN配置

配置需求或说明 1.1适用产品系列 本案例适用于如S7006、S7503E、S7506E、S7606、S10510、S10508等S7000、S7500E、S10500系列&#xff0c;且软件版本是V7的交换机 1.2配置需求及实现的效果 SWA和SWB的GE1/0/1分别连接两个会议室&#xff0c;PC1和PC2是会议用笔记本电脑&…

第八篇:强化学习值迭代及代码实现

你好&#xff0c;我是郭震&#xff08;zhenguo&#xff09; 前几天我们学习强化学习策略迭代&#xff0c;今天&#xff0c;强化学习第8篇&#xff1a;强化学习值迭代 值迭代是强化学习另一种求解方法&#xff0c;用于找到马尔可夫决策过程&#xff08;MDP&#xff09;中的最优值…

chatgpt赋能python:Python如何取两位小数?

Python如何取两位小数&#xff1f; 如果你是一个Python开发人员&#xff0c;想必你会遇到需要将数字取两位小数的情况。无论你是在处理金融数据&#xff0c;或者是在处理一些科学计算&#xff0c;都需要将结果保留到小数点后两位。在这篇文章中&#xff0c;我们将介绍如何在Py…

中国的互联网技术有多厉害?

1 很多人没有意识到&#xff0c;中国的互联网技术是相当厉害的。 给大家举几个例子。 我和朋友聊天的时候&#xff0c;手机上的app都在“侧耳倾听”&#xff0c;聊天的一些关键字很快就会出现在手机浏览器的搜索栏中。 携程会给我自动推荐景点&#xff0c;美团会给我推荐美食&…

大裁员继续,直到回归均值

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 关于裁员&#xff0c;不想再举个案&#xff0c;大家也都听烦了。还是给大家几个宏观数字吧。据专门追踪科技公司裁员人数的Layoffs.fyi网站统计&#xff0c;2023年以来&#xff0c;截至5月底&#xff…

chatgpt赋能python:Python断行:如何优雅地换行?

Python断行&#xff1a;如何优雅地换行&#xff1f; 简介 Python是一种直观、易于学习、优雅且精简的编程语言。但是&#xff0c;随着代码复杂度的增加&#xff0c;长行代码也变得越来越难以阅读。所以&#xff0c;如何正确地断行是编写整洁Python代码的关键之一。 为什么需…

Spark大数据处理学习笔记1.1 搭建Scala开发环境

文章目录 一、学习目标二、scala简介&#xff08;一&#xff09;Scala概述&#xff08;二&#xff09;函数式编程 三、windows上安装scala&#xff08;一&#xff09;到Scala官网下载Scala&#xff08;二&#xff09;安装Scala&#xff08;三&#xff09;配置Scala环境变量 四、…

前端——平台登录功能实战

这里写目录标题 一、登录界面1、新建LoginView.vue2、登录页面展示二、登录路由1、注册登录页面路由三、前端登录接口设计1、新建http.js2、新建user.js3、api.js四、登录页面调用登录接口五、前端配置路由守卫六、前端配置请求拦截器七、前端配置响应拦截器八、退出登录九、前…

简单易行的 Java 服务端生成动态 Word 文档下载

需求&#xff1a;某些合同&#xff0c;被制作成模板&#xff0c;以 Word 格式保存&#xff0c;输入相关的内容参数最终生成 Word 文档下载。这是企业级应用中很常见的需求。 解决方案&#xff1a;无非是模板技术&#xff0c;界定不变和变的内容&#xff0c;预留插值的标记&…

【最新计算机、电子毕业设计 本科 大专 设计+源码】

2022年 - 2023年 最新计算机、电子毕业设计 本科 大专 设计源码 下载前必看&#xff1a;纯小白教程&#xff0c;unity两种格式资源的使用方法&#xff0c;1打开现有项目、2导入package 大专毕设源码&#xff1a;数媒专业、计算机专业、电子专业通用50多款大专毕设小游戏【源码】…

一文说清Task及其调度问题

ask对于.NET的重要性毋庸置疑。通过最近的一些面试人员经历&#xff0c;发现很多人对与Task及其调度机制&#xff0c;以及线程和线程池之间的关系并没有清晰的认识。本文采用最简单的方式模拟了Task的实现&#xff0c;旨在说明Task是什么&#xff1f;它是如何被调度执行的&…

JUC源码分析:ReentrantLock

ReentrantLock进行上锁的流程如下图所示&#xff0c;我们将按照下面的流程分析ReentrantLock上锁的流程。 先进入ReentrantLock.lock方法。 再进入内部类NonfairSync的lock方法。 点击acquire方法进入AbstractQueuedSynchronizer.acquire方法。 进入tryAcquire方法回到Reentra…

【小林计网笔记】 IP篇

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 在这里插入图片描述 一、IP 基本认识1、IP的作用2、IP与MAC的关系 二、IP 地址的基础知识1、IP地址的定义2、IP地址的分类1、ABC类地址2、DE类地址3、IP地址分类的优…