设计模式六:策略模式

1、策略模式

策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立。

策略模式的角色:

  • 策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法。

  • 具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现。

  • 策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
    在这里插入图片描述

//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}

 // 具体的策略实现1
public class ConcreteStrategy1 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy1 method...");
    }
}

// 具体的策略实现2
public class ConcreteStrategy2 implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy2 method...");
    }
}

/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}

//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现
        IStrategy strategy = new ConcreteStrategy2();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}

示例

现实生活中我们到商场买东西的时候,卖场往往根据不同的客户制定不同的报价策略,比如针对新客户不打折扣,针对老客户打9折,针对VIP客户打8折…

//一般做法
//商城管理
import java.math.BigDecimal;
 
public class QuoteManager {
 
    public BigDecimal quote(BigDecimal originalPrice, String customType){
        if ("新客户".equals(customType)) {
            return this.quoteNewCustomer(originalPrice);
        }else if ("老客户".equals(customType)) {
            return this.quoteOldCustomer(originalPrice);
        }else if("VIP客户".equals(customType)){
            return this.quoteVIPCustomer(originalPrice);
        }
        //其他人员都是原价
        return originalPrice;
    }
 
    /**
     * 对VIP客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户打8折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 对老客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户打9折");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
 
    /**
     * 对新客户的报价算法
     * @param originalPrice 原价
     * @return 折后价
     */
    private BigDecimal quoteNewCustomer(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
 
}

//客户
public class Client {
    public static void main(String[] args) {
        QuoteManager quoteManager = new QuoteManager();
        BigDecimal price = quoteManager.quote(BigDecimal.valueOf(780.09), "VIP客户");
        System.out.println("支付: " + price + "元");
    }
}

这种方式下:

1、当我们新增一个客户类型的时候,首先要添加一个该种客户类型的报价算法方法,然后再quote方法中再加一个else if的分支,是不是感觉很是麻烦呢?而且这也违反了设计原则之一的开闭原则(open-closed-principle)

2.我们经常会面临这样的情况,不同的时期使用不同的报价规则,比如在各个节假日举行的各种促销活动时、商场店庆时往往都有普遍的折扣,但是促销时间一旦过去,报价就要回到正常价格上来。按照上面的代码我们就得修改if else里面的代码很是麻烦


策略模式:
在这里插入图片描述

//报价策略接口
public interface IQuoteStrategy {
    //获取折后价的价格
    BigDecimal getPrice(BigDecimal originalPrice);
}

//新客户的报价策略实现类
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("抱歉!新客户没有折扣!");
        return originalPrice;
    }
}

//老客户的报价策略实现
public class OldCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!老客户享有9折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}

//VIP客户的报价策略实现
public class VIPCustomerQuoteStrategy implements IQuoteStrategy {
    @Override
    public BigDecimal getPrice(BigDecimal originalPrice) {
        System.out.println("恭喜!VIP客户享有8折优惠!");
        originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP);
        return originalPrice;
    }
}
//报价上下文角色
public class QuoteContext {
    //持有一个具体的报价策略
    private IQuoteStrategy quoteStrategy;
 
    //注入报价策略
    public QuoteContext(IQuoteStrategy quoteStrategy){
        this.quoteStrategy = quoteStrategy;
    }
 
    //回调具体报价策略的方法
    public BigDecimal getPrice(BigDecimal originalPrice){
        return quoteStrategy.getPrice(originalPrice);
    }
}
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建老客户的报价策略
        IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
 
        //2.创建报价上下文对象,并设置具体的报价策略
        QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
 
        //3.调用报价上下文的方法
        BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
 
        System.out.println("折扣价为:" +price);
    }
}

策略模式下,新增客户类型或者修改原有客户类型,只需要新增一个实现类或者修改对应类即可

2、策略模式和工厂模式区别

策略模式和工厂模式在模式结构上,两者很相似:

在这里插入图片描述

区别:

用途和关注点

  • 策略模式属于行为类设计模式,关注对行为的封装,client注重的是结果

  • 工厂模式属于创建类设计模式,关注对象的创建,client注重的是获取类对象

解决问题:

  • 策略模式是为了解决的是策略的切换与扩展,更简洁的说是定义策略族,分别封装起来,让他们之间可以相互替换,策略模式让策略的变化独立于使用策略的客户。

  • 工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关。主要应用在多数据库选择,类库文件加载等。

  • 工厂相当于黑盒子,策略相当于白盒子;

3、策略模式和模板模式区别

策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)是两种不同的行为类设计模式,它们在实现上有一些明显的区别

在这里插入图片描述
在这里插入图片描述

  1. 目的和应用场景:
    • 策略模式: 主要用于定义一系列的算法,将每个算法封装起来,并使它们可以互相替换。客户端可以选择不同的策略对象,以达到不同的行为。
    • 模板方法模式: 主要用于定义一个算法的骨架,将一些步骤的实现延迟到子类。父类中定义了模板方法,该方法中的某些步骤的具体实现由子类决定。
  2. 关注点:
    • 策略模式: 关注的是算法的替换和客户端的选择。
    • 模板方法模式: 关注的是算法的骨架和具体步骤的延迟实现。
  3. 组成结构:
    • 策略模式: 主要包含环境类(Context)、策略接口(Strategy)和具体策略实现类(ConcreteStrategy)。
    • 模板方法模式: 主要包含抽象类(AbstractClass)、模板方法(Template Method)和具体实现步骤的方法。
  4. 灵活性和扩展性:
    • 策略模式: 策略可以相对独立地变化,客户端可以灵活地选择和切换不同的策略。
    • 模板方法模式: 算法的骨架是固定的,但某些步骤的具体实现可以在子类中进行扩展。
  5. 调用方式:
    • 策略模式: 客户端通常主动选择并设置具体的策略对象。
    • 模板方法模式: 算法的执行是由父类的模板方法触发的,子类可以通过扩展来影响某些步骤的具体实现。

策略模式关注的是定义一系列算法并使它们可以互相替换,而模板方法模式关注的是定义一个算法的骨架,将某些步骤的实现交给子类决定。它们分别适用于不同的设计需求和场景。

4、spring中的策略模式

4.1 ThreadPoolExecutor

在多线程编程中,我们经常使用线程池来管理线程,以减缓线程频繁的创建和销毁带来的资源的浪费,在创建线程池的时候,经常使用一个工厂类来创建线程池Executors,实际上Executors的内部使用的是类ThreadPoolExecutor.它有一个最终的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

这里面是线程池的重要参数:

  • corePoolSize:线程池中的核心线程数量,即使这些线程没有任务干,也不会将其销毁。

  • maximumPoolSize:线程池中的最多能够创建的线程数量。

  • keepAliveTime:当线程池中的线程数量大于corePoolSize时,多余的线程等待新任务的最长时间。

  • unit:keepAliveTime的时间单位。

  • workQueue:在线程池中的线程还没有还得及执行任务之前,保存任务的队列(当线程池中的线程都有任务在执行的时候,仍然有任务不断的提交过来,这些任务保存在workQueue队列中)。

  • threadFactory:创建线程池中线程的工厂。

  • handler:当线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。

在这里插入图片描述

RejectedExecutionHandler 是一个策略接口,用在当线程池中没有多余的线程来执行任务,并且保存任务的多列也满了(指的是有界队列),对仍在提交给线程池的任务的处理策略。

public interface RejectedExecutionHandler {
 
    /**
     *当ThreadPoolExecutor的execut方法调用时,并且ThreadPoolExecutor不能接受一个任务Task时,该方法就有可能被调用。
   * 不能接受一个任务Task的原因:有可能是没有多余的线程来处理,有可能是workqueue队列中没有多余的位置来存放该任务,该方法有可能抛出一个未受检的异常RejectedExecutionException
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

该策略接口有四个实现类

AbortPolicy:该策略是直接将提交的任务抛弃掉,并抛出RejectedExecutionException异常。

java复制代码/**
     * A handler for rejected tasks that throws a
     * <tt>RejectedExecutionException</tt>.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an <tt>AbortPolicy</tt>.
         */
        public AbortPolicy() { }
 
        /**
         * Always throws RejectedExecutionException.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always.
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException();
        }
    }

DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。

java复制代码/**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardPolicy</tt>.
         */
        public DiscardPolicy() { }
 
        /**
         * Does nothing, which has the effect of discarding task r.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。

java复制代码/**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries <tt>execute</tt>, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a <tt>DiscardOldestPolicy</tt> for the given executor.
         */
        public DiscardOldestPolicy() { }
 
        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。

java复制代码/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }
 
        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

类ThreadPoolExecutor中持有一个RejectedExecutionHandler接口的引用,以便在构造函数中可以由外部客户端自己制定具体的策略并注入。下面看一下其类图:
在这里插入图片描述

创建线程池的时候,根据具体场景,选择不同的拒绝策略

4.2 InstantiationStrategy

spring中bean的实例化过程如下:

InstantiationStrategy提供了实例化bean的不同策略:

public interface InstantiationStrategy {

/**
 * 默认的构造方法
 * @param bd
 * @param beanName
 * @param owner
 * @return
 * @throws BeansException
 */
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)throws BeansException;
/**
 * 指定构造方法
 * @param bd
 * @param beanName
 * @param owner
 * @param ctor
 * @param args
 * @return
 * @throws BeansException
 */

Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
        Constructor<?> ctor, Object... args) throws BeansException;
/**
 * 通过指定的工厂方法
 * @param bd
 * @param beanName
 * @param owner
 * @param factoryBean
 * @param factoryMethod
 * @param args
 * @return
 * @throws BeansException
 */
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
        @Nullable Object factoryBean, Method factoryMethod, Object... args)
        throws BeansException;

看具体的方法有子类实现并执行,类图如下:

使用策略模式,实例化bean的时候,根据传参的不同,选择不同的实例化策略

具体InstantiationStrategy实现及执行流程,搜索[相关文章](

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

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

相关文章

抖音爬虫批量视频提取功能介绍|抖音评论提取工具

抖音爬虫是指通过编程技术从抖音平台上获取视频数据的程序。在进行抖音爬虫时&#xff0c;需要注意遵守相关法律法规和平台规定&#xff0c;以确保数据的合法获取和使用。 一般来说&#xff0c;抖音爬虫可以实现以下功能之一&#xff1a;批量视频提取。这个功能可以用于自动化地…

【C++精简版回顾】7.析构函数

1.析构函数 class MM { public:MM() {}MM(const char* a) {name new char[strlen(a)1];strcpy(name, a);cout << name << endl;}~MM() {delete[] name;name nullptr;cout << "调用析构函数" << endl;} private:char* name; }; int main(…

论文摘要翻译 ,论文摘要怎么翻译成英文?

论文摘要翻译在学术论文写作中扮演着至关重要的角色&#xff0c;它如同明灯&#xff0c;引领着读者快速理解论文的核心观点与目的。那么&#xff0c;如何才能将论文摘要翻译得恰到好处呢&#xff1f;这其中又蕴含着怎样的奥秘&#xff1f; 专家指出&#xff0c;摘要作为正式论文…

使用uniapp实现小程序获取wifi并连接

一、背景 因业务需求&#xff0c;需要在小程序实现发现wifi和连接wifi。但由于Andriod和IOS有差异&#xff0c;所以实现起来有所区别。 先看官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/device/wifi.html 把连接基础流程了解后&#xff0c;发现二者…

Jenkins的使用GIT(4)

Jenkins的使用GIT 20211002 我们使用 Jenkins 集成外部 Git 仓库&#xff0c;实现对真实代码的拉取和构建。在这里&#xff0c;我们选用 Coding/Github/Gitee 等都可以作为我们的代码源 1 生成公钥私钥 首先&#xff0c;我们先来配置公钥和私钥。这是 Jenkins 访问 Git 私有库…

springboot大学生体质测试管理系统源码和论文

大学生体质测试管理系统提供给用户一个简单方便体质测试管理信息&#xff0c;通过留言区互动更方便。本系统采用了B/S体系的结构&#xff0c;使用了java技术以及MYSQL作为后台数据库进行开发。系统主要分为系统管理员、教师和用户三个部分&#xff0c;系统管理员主要功能包括首…

【高德地图】Android高德地图初始化定位并显示小蓝点

&#x1f4d6;第3章 初始化定位并显示小蓝点 ✅第1步&#xff1a;配置AndroidManifest.xml✅第2步&#xff1a;设置定位蓝点✅第3步&#xff1a;初始化定位✅完整代码 ✅第1步&#xff1a;配置AndroidManifest.xml 在application标签下声明Service组件 <service android:n…

主流的开发语言和开发环境介绍

个人浅见&#xff0c;不喜勿喷&#xff0c;谢谢 软件开发是一个涉及多个方面的复杂过程&#xff0c;其中包括选择合适的编程语言和开发环境。编程语言是软件开发的核心&#xff0c;它定义了程序员用来编写指令的语法和规则。而开发环境则提供了编写、测试和调试代码的工具和平台…

Shopee提现有哪些要求?提现到个人账户还是公司账户?站斧浏览器

Shopee提现有哪些要求&#xff1f; 中国内地卖家提现至境内同名对公账户/法定代表人个人账户&#xff0c;支持所有主流银行、大部分农村信用社和村镇银行&#xff0c;部分银行需要提供联行号;中国香港卖家提现或付款到香港或全球银行账户&#xff0c;支持所有主流银行。 Shop…

用友NC65与用友NCC对接集成NC65-凭证列表查询打通凭证新增

用友NC65与用友NCC对接集成NC65-凭证列表查询打通凭证新增 数据源平台:用友NC65 用友NC是为集团与行业企业提供的全线管理软件产品&#xff0c;由亚太本土最大的企业管理软件提供商用友公司研发提供&#xff0c;用友NC率先采用J2EE架构和先进开放的集团级开发平台UAP&#xff0…

适用于生物行业的样本管理系统

在生物样本管理系统的应用中&#xff0c;我们首先需要了解生物样本的特点和要求。生物样本具有多样性和易变性&#xff0c;需要被妥善保存和跟踪&#xff0c;以确保其质量和可用性。 因此&#xff0c;一个有效的生物样本管理系统需要具备以下特点&#xff1a; 全面性&#xff1…

深度剖析Selenium与Scrapy的黄金组合:实现动态网页爬虫

在当今互联网时代&#xff0c;大量网站采用动态网页技术呈现信息&#xff0c;这给爬虫技术提出了新的挑战。本文将带您深入探讨如何应对动态网页的爬取难题&#xff0c;结合Python爬虫框架Scrapy和自动化测试工具Selenium进行实战&#xff0c;为您揭示动态网页爬取的技术奥秘。…

现货黄金短线走高是机会还是风险?

在投资市场上&#xff0c;短线交易一般是指投资者在两三个交易日或一两个星期内&#xff0c;通过低买高卖获取差价收益的买卖行为。做短线交易的人&#xff0c;一旦价格到达自己的止损位置就会果断地离场&#xff0c;然后重新等待入市的机会&#xff0c;或者直接参与其他的品种…

链表 任意位置插入一个节点

那么&#xff0c;内存中发生了什么事情呢&#xff1f; 当程序开始执行时&#xff0c;最初将调用main函数&#xff0c;栈中的部分内存被分配用于执行函数。 所有局部变量以及该函数的执行状态都保存在这个特定的区域&#xff0c;我们也将其称为函数的栈帧。 在此main函数中&…

【FreeRTOS基础入门】软件定时器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、软件定时器的介绍1.1 软件定时器的特性1.2 软件定时器的特性1.3 守护任务 二、软件定时器的使用2.1 回调函数2.2 创建定时器创建动态定时器创建静态定时器 …

快速搭建网站流程

快速搭建网站流程 1、购买服务器 国内网服务器都可以、国内的腾讯云和阿里云操作什么都差不多&#xff0c; 目前腾讯云服务器举例、买个轻量级服务器或者云服务器都行。然后我们后续会通过宝塔面板进行操作&#xff0c;系统以centos 举例。 或者选第二个 2、通过SSH登录到…

Python环境无缝移植,拷贝到其他机器也能使用

1.前景 下载&#xff1a;Python版本下载 对于Python原生环境来说&#xff0c;假设读者各位&#xff0c;安装了一个Python环境&#xff0c;环境变量&#xff0c;路径&#xff0c;都是安装包自动配好的。 相信大家有的人是单机开发或者有其他需求&#xff0c;就会发现&#xff…

Hudi程序导致集群RPC偏高问题分析

1、背景 Hudi程序中upsert操作频繁&#xff0c;过多的删除和回滚操作,导致集群RPC持续偏高 2、描述 hudi采用的是mvcc设计&#xff0c;提供了清理工具cleaner来把旧版本的文件分片删除&#xff0c;默认开启了清理功能&#xff0c;可以防止文件系统的存储空间和文件数量的无限…

Selenium安装与配置

文章目录 一、selenium安装1. Python环境准备&#xff1a;2. 安装Selenium&#xff1a;3. 浏览器驱动安装&#xff1a;4. 验证安装&#xff1a; 二、常见问题1. Selenium版本与浏览器驱动程序不兼容&#xff1a;2. 浏览器驱动程序路径未正确设置&#xff1a; Selenium是一个用于…

k8s(3)

目录 一.K8S的三种网络 flannel的三种模式: 在 node01 节点上操作&#xff1a; calico的 三种模式&#xff1a; flannel 与 calico 的区别&#xff1f; 二.CoreDNS 在所有 node 节点上操作&#xff1a; 在 master01 节点上操作&#xff1a; ​编辑 DNS 解析测试&#…