JAVA工厂方法模式详解

工厂方法模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  • 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

《设计模式》一书中,工厂模式被分为了三种:简单工厂、工厂方法和抽象工厂。(不过,在书中作者将简单工厂模式看作是工厂方法模式的一种特例。)

接下来我会介绍三种工厂模式的原理及使用

  • 简单工厂模式(不属于GOF的23种经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

1 需求: 模拟发放奖品业务

需求: 为了让我们的案例更加贴近实际开发, 这里我们来模拟一下互联网电商中促销拉新下的业务场景, 新用户注册立即参与抽奖活动 ,奖品的种类有: 打折券, 免费优酷会员,小礼品.

在这里插入图片描述

2 原始开发方式

不考虑设计原则,不使用设计模式的方式进行开发

在不考虑任何代码的可扩展性的前提下,只为了尽快满足需求.我们可以这样去设计这个业务的代码结构:

1) 实体类

名称描述
AwardInfo获奖信息对应实体类
DiscountInfo打折券信息对应实体类
YouKuMember优酷会员对应实体类
SmallGiftInfo小礼品信息对应实体类
DiscountResult打折券操作响应结果封装类
public class AwardInfo {

    private String uid; //用户唯一ID

    private Integer awardType; //奖品类型: 1 打折券 ,2 优酷会员,3 小礼品

    private String awardNumber; //奖品编号

    Map<String, String> extMap; //额外信息
    
}

public class DiscountInfo {

    //属性信息省略......
}

public class YouKuMember {

    //属性信息省略......
}

public class SmallGiftInfo {

    private String userName;              // 用户姓名
    private String userPhone;             // 用户手机
    private String orderId;               // 订单ID
    private String relAddress;            // 收货地址
    
}

public class DiscountResult {

    private String status; // 状态码
    private String message; // 信息
}

2) 服务层

名称功能描述
DiscountServiceDiscountResult sendDiscount(String uid,String number)模拟打折券服务
YouKuMemberServicevoid openMember(String bindMobile , String number)模拟赠送优酷会员服务
SmallGiftServiceBoolean giveSmallGift(SmallGiftInfo smallGiftInfo)模拟礼品服务
public class DiscountService {

    public DiscountResult sendDiscount(String uid, String number){

        System.out.println("向用户发放打折券一张: " + uid + " , " + number);
        return new DiscountResult("200","发放打折券成功");
    }
}

public class YouKuMemberService {

    public void openMember(String bindMobile , String number){

        System.out.println("发放优酷会员: " + bindMobile + " , " + number);
    }
}

public class SmallGiftService {

    public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo){

        System.out.println("小礼品已发货,获奖用户注意查收! " + JSON.toJSON(smallGiftInfo));
        return true;
    }
}

3) 控制层

名称功能描述
DeliverControllerResponseResult awardToUser(AwardInfo awardInfo)按照类型的不同发放商品
奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
public class DeliverController {

    /**
     * 按照类型的不同发放商品
     *     奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
     */
    public void awardToUser(AwardInfo awardInfo){

        if(awardInfo.getAwardType() == 1){ //打折券
            DiscountService discountService = new DiscountService();
            DiscountResult result = discountService.sendDiscount(awardInfo.getUid(), awardInfo.getAwardNumber());
            System.out.println("打折券发放成功!"+ JSON.toJSON(result));

        }else if(awardInfo.getAwardType() == 2){ //优酷会员
            //获取用户手机号
            String bindMobile = awardInfo.getExtMap().get("phone");

            //调用service
            YouKuMemberService youKuMemberService = new YouKuMemberService();
            youKuMemberService.openMember(bindMobile,awardInfo.getAwardNumber());
            System.out.println("优酷会员发放成功!");

        }else if(awardInfo.getAwardType() == 3){ /*
            小礼品
            封装收货用户信息
            */
            SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
            smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));
            smallGiftInfo.setOrderId(UUID.randomUUID().toString());
            smallGiftInfo.setRelAddress(awardInfo.getExtMap().get("adderss"));

            SmallGiftService smallGiftService = new SmallGiftService();
            Boolean isSuccess = smallGiftService.giveSmallGift(smallGiftInfo);
            System.out.println("小礼品发放成功!" + isSuccess);
        }
    }

}

4) 测试

通过单元测试,来对上面的接口进行测试,验证代码质量.

public class TestApi01 {

    //测试发放奖品接口
    @Test
    public void test01(){

        DeliverController deliverController = new DeliverController();

        //1. 发放打折券优惠
        AwardInfo info1 = new AwardInfo();
        info1.setUid("1001");
        info1.setAwardType(1);
        info1.setAwardNumber("DEL12345");

        deliverController.awardToUser(info1);


        //2. 发放优酷会员
        AwardInfo info2 = new AwardInfo();
        info2.setUid("1002");
        info2.setAwardType(2);
        info2.setAwardNumber("DW12345");
        Map<String,String> map = new HashMap<>();
        map.put("phone","13512341234");
        info2.setExtMap(map);

        deliverController.awardToUser(info2);



        //2. 发放小礼品
        AwardInfo info3 = new AwardInfo();
        info3.setUid("1003");
        info3.setAwardType(3);
        info3.setAwardNumber("SM12345");
        Map<String,String> map2 = new HashMap<>();
        map2.put("username","大远");
        map2.put("phone","13512341234");
        map2.put("address","北京天安门");
        info3.setExtMap(map2);

        deliverController.awardToUser(info3);
    }
}

对于上面的实现方式,如果我们有想要添加的新的奖品时,势必要改动DeliverController的代码,违反开闭原则.而且如果有的抽奖接口出现问题,那么对其进行重构的成本会非常高.

除此之外代码中有一组if分支判断逻辑,现在看起来还可以,但是如果经历几次迭代和拓展,后续ifelse肯定还会增加.到时候接手这段代码的研发将会十分痛苦.

3 简单工厂模式

3.1 简单工厂模式介绍

简单工厂不是一种设计模式,反而比较像是一种编程习惯。简单工厂模式又叫做静态工厂方法模式(static Factory Method pattern),它是通过使用静态方法接收不同的参数来返回不同的实例对象.

实现方式:

​ 定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

适用场景:
  (1)需要创建的对象较少。
  (2)客户端不关心对象的创建过程。

3.2 简单工厂原理

简单工厂包含如下角色:

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品 :实现或者继承抽象产品的子类
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
image-20220530160637842
3.3 简单工厂模式重构代码

1) service

/**
 * 免费商品发放接口
 **/
public interface IFreeGoods {

    ResponseResult sendFreeGoods(AwardInfo awardInfo);

}
/**
 * 模拟打折券服务
 **/
public class DiscountFreeGoods implements IFreeGoods {

    @Override
    public ResponseResult sendFreeGoods(AwardInfo awardInfo) {

        System.out.println("向用户发放一张打折券: " + awardInfo.getUid() + " , " + awardInfo.getAwardNumber());
        return new ResponseResult("200","打折券发放成功!");
    }
}

/**
 * 小礼品发放服务
 **/
public class SmallGiftFreeGoods implements IFreeGoods {

    @Override
    public ResponseResult sendFreeGoods(AwardInfo awardInfo) {

        SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
        smallGiftInfo.setUserPhone(awardInfo.getExtMap().get("phone"));
        smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));
        smallGiftInfo.setAddress(awardInfo.getExtMap().get("address"));
        smallGiftInfo.setOrderId(UUID.randomUUID().toString());

        System.out.println("小礼品发放成,请注意查收: " + JSON.toJSON(smallGiftInfo));
        return new ResponseResult("200","小礼品发送成功",smallGiftInfo);
    }
}

/**
 * 优酷 会员服务
 **/
public class YouKuMemberFreeGoods implements IFreeGoods {

    @Override
    public ResponseResult sendFreeGoods(AwardInfo awardInfo) {

        String phone = awardInfo.getExtMap().get("phone");
        System.out.println("发放优酷会员成功,绑定手机号: " + phone);
        return new ResponseResult("200","优酷会员发放成功!");
    }
}

2) factory

/**
 * 具体工厂: 生成免费商品
 **/
public class FreeGoodsFactory {

    public static IFreeGoods getInstance(Integer awardType){

        IFreeGoods iFreeGoods = null;

        if(awardType == 1){  //打折券

            iFreeGoods = new DiscountFreeGoods();
        }else if(awardType == 2){ //优酷会员

            iFreeGoods = new YouKuMemberFreeGoods();
        }else if(awardType == 3){ //小礼品

            iFreeGoods = new SmallGiftFreeGoods();
        }

        return iFreeGoods;
    }
}

3)controller

public class DeliverController {

    //发放奖品
    public ResponseResult awardToUser(AwardInfo awardInfo){

        try {
            IFreeGoods freeGoods = FreeGoodsFactory.getInstance(awardInfo.getAwardTypes());
            ResponseResult responseResult = freeGoods.sendFreeGoods(awardInfo);
            return responseResult;
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseResult("201","奖品发放失败!");
        }
    }
}

4) 测试

通过单元测试,来对上面的接口进行测试,验证代码质量.

public class TestApi02 {

    DeliverController deliverController = new DeliverController();

    @Test
    public void test01(){

        //1. 发放打折券优惠
        AwardInfo info1 = new AwardInfo();
        info1.setUid("1001");
        info1.setAwardTypes(1);
        info1.setAwardNumber("DEL12345");

        ResponseResult result = deliverController.awardToUser(info1);
        System.out.println(result);

    }

    @Test
    public void test02(){

        //2. 发放优酷会员
        AwardInfo info2 = new AwardInfo();
        info2.setUid("1002");
        info2.setAwardTypes(2);
        info2.setAwardNumber("DW12345");
        Map<String,String> map = new HashMap<>();
        map.put("phone","13512341234");
        info2.setExtMap(map);

        ResponseResult result1 = deliverController.awardToUser(info2);
        System.out.println(result1);

    }

    @Test
    public void test03(){

        //3. 发放小礼品
        AwardInfo info3 = new AwardInfo();
        info3.setUid("1003");
        info3.setAwardTypes(3);
        info3.setAwardNumber("SM12345");
        Map<String,String> map2 = new HashMap<>();
        map2.put("username","大远");
        map2.put("phone","13512341234");
        map2.put("address","北京天安门");
        info3.setExtMap(map2);

        ResponseResult result2 = deliverController.awardToUser(info3);
        System.out.println(result2);
    }
}
3.4 简单工厂模式总结

优点:

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点:

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

4 工厂方法模式

4.1 工厂方法模式介绍

工厂方法模式 Factory Method pattern,属于创建型模式.

概念: 定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

4.2 工厂方法模式原理

工厂方法模式的目的很简单,就是封装对象创建的过程,提升创建对象方法的可复用性

工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

我们直接来看看工厂方法模式的 UML 图:

image-20220530160637842
4.3 工厂方法模式重构代码

为了提高代码扩展性,我们需要将简单工厂中的if分支逻辑去掉,通过增加抽象工厂(生产工厂的工厂)的方式,让具体工厂去进行实现,由具体工厂来决定实例化哪一个具体的产品对象.

抽象工厂

public interface FreeGoodsFactory {

    IFreeGoods getInstance();
}

具体工厂

public class DiscountFreeGoodsFactory implements FreeGoodsFactory {

    @Override
    public IFreeGoods getInstance() {
        return new DiscountFreeGoods();
    }
}

public class SmallGiftFreeGoodsFactory implements FreeGoodsFactory {
    @Override
    public IFreeGoods getInstance() {

        return new SmallGiftFreeGoods();
    }
}

Controller

public class DeliverController {

    /**
     * 按照类型的不同发放商品
     */
    public ResponseResult awardToUser(AwardInfo awardInfo){

        FreeGoodsFactory freeGoodsFactory = null;

        if(awardInfo.getAwardType() == 1){

            freeGoodsFactory = new DiscountFreeGoodsFactory();
        }else if(awardInfo.getAwardType() == 2){

            freeGoodsFactory = new SmallGiftFreeGoodsFactory();
        }

        IFreeGoods freeGoods = freeGoodsFactory.getInstance();

        System.out.println("=====工厂方法模式========");
        ResponseResult result = freeGoods.sendFreeGoods(awardInfo);

        return result;
    }

}

从上面的代码实现来看,工厂类对象的创建逻辑又耦合进了 awardToUser() 方法中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。

那怎么 来解决这个问题呢?

我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

/**
 * 用简单方法模式实现: 工厂的工厂,作用是不需要每次创建新的工厂对象
 **/
public class FreeGoodsFactoryMap {

    private static final Map<Integer,FreeGoodsFactory> cachedFactories = new HashMap<>();

    static{
        cachedFactories.put(1, new DiscountFreeGoodsFactory());
        cachedFactories.put(2, new SmallGiftFreeGoodsFactory());
    }

    public static FreeGoodsFactory getParserFactory(Integer type){
        if(type == 1){
            FreeGoodsFactory freeGoodsFactory = cachedFactories.get(1);
            return freeGoodsFactory;
        }else if(type ==2){
            FreeGoodsFactory freeGoodsFactory = cachedFactories.get(2);
            return freeGoodsFactory;
        }

        return null;
    }
}

Controller

/**
 * 发放奖品接口
 **/
public class DeliverController {

    /**
     * 按照类型的不同发放商品
     */
    public ResponseResult awardToUser(AwardInfo awardInfo){

        //根据类型获取工厂
        FreeGoodsFactory goodsFactory = FreeGoodsFactoryMap.getParserFactory(awardInfo.getAwardType());

        //从工厂中获取对应实例
        IFreeGoods freeGoods = goodsFactory.getInstance();

        System.out.println("=====工厂方法模式========");
        ResponseResult result = freeGoods.sendFreeGoods(awardInfo);
        return result;
    }
}

现在我们的代码已经基本上符合了开闭原则,当有新增的产品时,我们需要做的事情包括:

  1. 创建新的产品类,并且让该产品实现抽象产品接口
  2. 创建产品类对应的具体工厂,并让具体工厂实现抽象工厂
  3. 将新的具体工厂对象,添加到FreeGoodsFactoryMap的cachedFactories中即可,需要改动的代码改动的非常少.
4.4 工厂方法模式总结

工厂方法模优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

什么时候使用工厂方法模式

  • 需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等。
  • 创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权 token 信息,配置文件等。
  • 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
  • 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。
  • 希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。

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

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

相关文章

如何结合ChatGPT生成个人魔法咒语词库

3.6.1 ChatGPT辅助力AI绘画 3.6.1.1 给定主题让ChatGPT直接描述 上面给了一个简易主题演示一下&#xff0c;这是完全我没有细化的提问&#xff0c;然后把直接把这些关键词组合在一起。 关键词&#xff1a; 黄山的美景&#xff0c;生机勃勃&#xff0c;湛蓝天空&#xff0c;青…

python使用Netmiko库配置路由器

目录 一&#xff1a;介绍 二&#xff1a;查看路由器接口信息 三&#xff1a;配置ip地址 四&#xff1a;配置防火墙 五&#xff1a;备份配置信息 一&#xff1a;介绍 Netmiko 是一个 Python 库&#xff0c;用于自动化网络设备的交互。它使用 Paramiko 作为其底层库来执行 S…

VSCode 安装LLDB调试器(OS X)并启动调试

插件&#xff1a;&#xff08;LLDB插件安装&#xff09; 安装这个版本不好弄错了&#xff0c;CodeLLDB&#xff08;名字&#xff09; 配置&#xff1a;&#xff08;LLDB启动调试&#xff09; {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更…

[ChatGPT们】ChatGPT 如何辅助编程初探

主页&#xff1a;元存储的博客 全文 9000 字&#xff0c; 原创请勿转载。 我没有写过诗&#xff0c;但有人说我的代码像诗一样优雅 -- 雷军 图片来源&#xff1a;https://www.bilibili.com/video/BV1zL411X7oS/ 1. 引言 作为一个程序员&#xff0c;我们不仅要熟悉各种编程语…

vit细粒度图像分类(十)TransFG学习笔记

1.摘要 细粒度视觉分类(FGVC)是一项非常具有挑战性的任务&#xff0c;它旨在从子类别中识别对象&#xff0c;这是由于类间固有的微妙差异。现有的大部分工作主要是通过重用骨干网络提取检测到的判别区域的特征来解决这一问题。然而&#xff0c;这种策略不可避免地使管道变得复…

神经网络 | 基于多种神经网络模型的轴承故障检测

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本文主要源自《第二届全国技能大赛智能制造工程技术项目比赛试题&#xff08;样题&#xff09; 模块 E 工业大数据与人工智能应用》&#xff0c;基于给出的已知轴承状态的振动信号样本&#xff0c;对数据进行分析&#xff0c;建…

修改MFC图标

摘要&#xff1a;本文主要讲解了MFC程序窗口图标的添加、任务栏、底部托盘的图标添加&#xff0c;以及所生成的exe文件图标的添加。 ​​​​​​​1、在资源视图添加Icon资源 透明图标怎么制作&#xff1f; 1&#xff09;点击图片》右键&#xff1a;使用画图3D进行编辑 2&a…

关于Django部署

首先了解一下开发环境服务器跟生产环境服务器有何不同。 一、我们通过 python manage.py runserver 启动开发环境服务器&#xff0c;这条命令背后做了哪些事情&#xff1f; 1、首先加载Django项目的设置&#xff08;settings&#xff09; 2、检查数据库迁移&#xff0c;确保数…

蓝桥杯备战——13.PCF8591芯片的使用

目录 1.芯片简介2.读写时序3.控制字4.代码封装库5.原理图分析6.使用示例 1.芯片简介 截取自NXP的PCF8591芯片数据手册&#xff0c;我把重点关注部分划出来了&#xff0c;请务必自行阅读一遍数据手册&#xff01; 2.读写时序 ①器件地址&#xff1a; Bit0决定是读还是写操作&…

python打造光斑处理系统7:沿割线的像素灰度分布

文章目录 单角度切割多角度切割绘图 光斑处理&#xff1a;python处理高斯光束的图像 光斑处理系统&#xff1a; 程序框架&#x1f31f;打开图像&#x1f31f;参数对话框/伪彩映射&#x1f31f;裁切ROI光强分布&#x1f31f;高斯拟合 单角度切割 在查看光斑分布时&#xff0c…

【C生万物】初始C语言

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

合并分支rebase和merge的区别

文章目录 一、前言1.1、master分支1.2、dev分支 二、合并2.1、git merge2.2、git rebase 三、总结四、最后 一、前言 实际开发工作的时候&#xff0c;我们都是在自己的分支开发&#xff0c;然后将自己的分合并到主分支&#xff0c;那合并分支用2种操作&#xff0c;这2种操作有…

maven项目管理工具安装和配置

文章目录 1.1 软件下载安装1.1.2 软件安装 1.2 软件配置1.2.1 软件环境配置1.2.2 软件版本测试1.2.3 maven 配置1.2.3.1 仓库配置1.2.3.2 镜像配置1.2.3.3 配置 JDK 1.3 IDEA 结合 Maven 使用 1.1 软件下载安装 首先我们需要去 Maven 官方下载安装软件&#xff0c;本文使用的是…

【深度测试】看到技术方案后,该怎么进行分析和测试

测试左移的思想&#xff0c;讲究尽早测试&#xff0c;测试是一系列的行为&#xff0c;并不一定要等代码运行起来才能测&#xff0c;下面会分享一些经验&#xff0c;提供大家参考。 一、静态分析 1.1 分析方法调用链 目标&#xff1a;梳理结构&#xff0c;化繁为简 原理&#…

Qt多语言翻译

Qt多语言翻译概述 Qt提供了非常简单易用的多语言翻译机制&#xff0c;其核心类为QTranslator.概括来说就是利用Qt的lupdate工具将项目中所有tr函数包裹的字符串提取到.ts文件中&#xff0c;然后使用Qt Linguist由专门的翻译人员对提取的.ts文件进行逐个单词短语的翻译工作. 翻译…

springboot153相亲网站

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

在WORD中设置公式居中编号右对齐设置方式

1 软件环境 Office Microsoft Office LTSC 专业增强版2021 2 最终效果 3 操作步骤 编辑公式&#xff1b;光标定位到公式的最后&#xff08;不是行的最后&#xff09;&#xff1b;输入#编号光标定位在公式最后&#xff08;不是行的最后&#xff09;&#xff0c;按Enter键回车…

【Linux系统 01】Vim工具

目录 一、Vim概述 1. 文件打开方式 2. 模式切换 二、命令模式 1. 移动与跳转 2. 复制与粘贴 3. 剪切与撤销 三、编辑模式 1. 插入 2. 替换 四、末行模式 1. 保存与退出 2. 查找与替换 3. 分屏显示 4. 命令执行 一、Vim概述 1. 文件打开方式 vim 文件路径&#…

【CSS】css获取子元素的父元素,即通过子元素选择父元素(使用CSS伪类 :has() :not() )

这里写目录标题 一、:has获取第一个div获取包含 a.active 的 li获取第二个div 二、:not除了类名为active 的 a,其他的a的字体都为18px <div><h1>标题</h1></div><div><ul><li><a href"#" class"active">测…

Filter与Listener(Java Web)

Filter与Listener(Java Web) 概念&#xff1a;Filter表示过滤器&#xff0c;是JavaWeb三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊的功能。过滤器一般完成一些通用的操作&#xff0c;比如&#xff1a;权限控制、…