设计模式之【策略模式】,去掉繁琐的if-else,实现算法的动态替换

news2024/11/22 15:11:56

文章目录

  • 一、什么是策略模式
    • 1、策略模式应用场景
    • 2、状态模式与策略模式的区别
    • 3、策略模式优缺点
    • 4、策略模式的三大角色
  • 二、实例
    • 1、策略模式的一般写法
    • 2、促销活动案例
    • 3、网购订单支付案例
    • 4、DispatcherServlet的优化
    • 5、文件排序案例
  • 三、源码中的策略模式
    • 1、Comparator接口
    • 2、Spring的InstantiationStrategy
    • 3、Spring的Resource

一、什么是策略模式

策略模式(Strategy Pattern)又叫政策模式(Policy Pattern),它是将定义的算法家族分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属于行为型模式。

策略模式使用的就是面向对象的继承和多态机制,从而实现同一行为在不同场景下具备不同实现。

1、策略模式应用场景

策略模式在生活中应用也非常多。比如一个人的交税比率与他的工资有关,不同工资对应不同的税率。再比如互联网移动支付,每次下单后付款都需要选择支付方式。

策略模式可以解决在有多种算法相似的情况下,使用if-else或者switch-case所带来的复杂性和臃肿性,策略模式通常适用于以下场景:

  • 针对同一类型问题,有多种处理方式,每一种都能独立解决问题;
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句;
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为;
  • 算法需要自由切换的场景;
  • 需要屏蔽算法规则的场景。

2、状态模式与策略模式的区别

状态模式和策略模式的UML类图架构几乎完全一样,但他们的应用场景是不一样的。策略模式多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法;而状态模式各个状态间是存在相互关系的,彼此之间在一定条件下存在自动切换状态效果,且用户无法指定状态,只能设置初始状态。

3、策略模式优缺点

优点:

  • 策略类之间可以自由切换:由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展:增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

4、策略模式的三大角色

在这里插入图片描述
策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略、算法的直接访问,封装可能存在的变化。

注:策略模式中的上下文环境(Context),其职责本来是隔离客户端与策略类的耦合,让客户端完全与上下文环境沟通,无需关心具体策略。

二、实例

1、策略模式的一般写法

//抽象策略类 Strategy
public interface IStrategy {
    void algorithm();
}
//具体策略类 ConcreteStrategy
public class ConcreteStrategyA implements IStrategy {
    public void algorithm() {
        System.out.println("Strategy A");
    }
}
//具体策略类 ConcreteStrategy
public class ConcreteStrategyB implements IStrategy {
    public void algorithm() {
        System.out.println("Strategy B");
    }
}
//上下文环境
public class Context {
    private IStrategy mStrategy;

    public Context(IStrategy strategy) {
        this.mStrategy = strategy;
    }

    public void algorithm() {
        this.mStrategy.algorithm();
    }
}
public class Test {
    public static void main(String[] args) {
        //选择一个具体策略
        IStrategy strategy = new ConcreteStrategyA();
        //来一个上下文环境
        Context context = new Context(strategy);
        //客户端直接让上下文环境执行算法
        context.algorithm();
    }
}

2、促销活动案例

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
在这里插入图片描述

// 定义百货公司所有促销活动的共同接口
public interface Strategy {
	void show();
}
// 定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
//为春节准备的促销活动A
public class StrategyA implements Strategy {
	public void show() {
		System.out.println("买一送一");
	}
}
//为中秋准备的促销活动B
public class StrategyB implements Strategy {
	public void show() {
		System.out.println("满200元减50元");
	}
}
//为圣诞准备的促销活动C
public class StrategyC implements Strategy {
	public void show() {
		System.out.println("满1000元加一元换购任意200元以下商品");
	}
}
// 定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
public class SalesMan {
	//持有抽象策略角色的引用
	private Strategy strategy;
	public SalesMan(Strategy strategy) {
		this.strategy = strategy;
	}
	//向客户展示促销活动
	public void salesManShow(){
		strategy.show();
	}
}
// 测试类
public class Client {
    public static void main(String[] args) {
        //春节来了,使用春节促销活动
        SalesMan salesMan = new SalesMan(new StrategyA());
        //展示促销活动
        salesMan.salesManShow();

        System.out.println("==============");
        //中秋节到了,使用中秋节的促销活动
        salesMan.setStrategy(new StrategyB());
        //展示促销活动
        salesMan.salesManShow();

        System.out.println("==============");
        //圣诞节到了,使用圣诞节的促销活动
        salesMan.setStrategy(new StrategyC());
        //展示促销活动
        salesMan.salesManShow();
    }
}

此时,我们发现,上面的测试代码放到实际业务场景其实并不实用,因为我们做活动时往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠,所以我们代码通常会这样写:

public class Client {
    public static void main(String[] args) {
        SalesMan salesMan = null;
        String saleKey = "A";
        
        if(saleKey.equals("A")){
            //春节来了,使用春节促销活动
            salesMan = new SalesMan(new StrategyA());
        } else if (saleKey.equals("B")) {
            //中秋节到了,使用中秋节的促销活动
            salesMan = new SalesMan(new StrategyB());
        } // ...

        //展示促销活动
        salesMan.salesManShow();
    }
}

这样改造之后,满足了业务需求,客户可以根据自己的需求选择不同的优惠策略了。但是这里的if-else随着促销活动的增多会越来越复杂,我们可以使用单例模式和工厂模式进行优化:

public class SalesMan {

    public static final String SaleKeyA = "A";
    public static final String SaleKeyB = "B";
    public static final String SaleKeyC = "C";

    private static Map<String, Strategy> sales = new HashMap<String, Strategy>();

    static {
        sales.put(SaleKeyA, new StrategyA());
        sales.put(SaleKeyB, new StrategyB());
        sales.put(SaleKeyC, new StrategyC());
    }

    public Strategy getStrategy(String key) {
        Strategy strategy = sales.get(key);
        if(strategy == null){
            throw new RuntimeException("策略有误");
        }
        return strategy;
    }
}

public class Client {
    public static void main(String[] args) {
        SalesMan salesMan = new SalesMan();
        String saleKey = "A";

        Strategy strategy = salesMan.getStrategy(saleKey);

        //展示促销活动
        strategy.show();
    }
}

3、网购订单支付案例

我们在网购下单时,会提示选择支付方式,通常会有支付宝、微信、银联等等支付方式,如果没选择,系统也会使用默认的支付方式,我们使用策略模式来模拟此场景:

// 支付状态包装类
public class MsgResult {
    private int code;
    private Object data;
    private String msg;

    public MsgResult(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "MsgResult{" +
                "code=" + code +
                ", data=" + data +
                ", msg='" + msg + '\'' +
                '}';
    }
}

// 定义支付逻辑,具体支付交由子类实现
public abstract class Payment {

    public abstract String getName();

    //通用逻辑放到抽象类里面实现
    public MsgResult pay(String uid, double amount){
        //余额是否足够
        if(queryBalance(uid) < amount){
            return new MsgResult(500,"支付失败","余额不足");
        }
        return new MsgResult(200,"支付成功","支付金额" + amount);
    }

    protected abstract double queryBalance(String uid);
}

// 定义具体支付方式
public class AliPay extends Payment {
    public String getName() {
        return "支付宝";
    }

    protected double queryBalance(String uid) {
        return 900;
    }
}
public class JDPay extends Payment {
    public String getName() {
        return "京东白条";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}
public class UnionPay extends Payment {
    public String getName() {
        return "银联支付";
    }

    protected double queryBalance(String uid) {
        return 120;
    }
}
public class WechatPay extends Payment {
    public String getName() {
        return "微信支付";
    }

    protected double queryBalance(String uid) {
        return 263;
    }
}

// 策略管理类
public class PayStrategy {
    public static  final String ALI_PAY = "AliPay";
    public static  final String JD_PAY = "JdPay";
    public static  final String WECHAT_PAY = "WechatPay";
    public static  final String UNION_PAY = "UnionPay";
    public static  final String DEFAULT_PAY = ALI_PAY;

    private static Map<String,Payment> strategy = new HashMap<String,Payment>();

    static {
        strategy.put(ALI_PAY,new AliPay());
        strategy.put(JD_PAY,new JDPay());
        strategy.put(WECHAT_PAY,new WechatPay());
        strategy.put(UNION_PAY,new UnionPay());
    }

    public static Payment get(String payKey){
        if(!strategy.containsKey(payKey)){
            return strategy.get(DEFAULT_PAY);
        }
        return strategy.get(payKey);
    }
}

// 订单类
public class Order {
    private String uid;
    private String orderId;
    private double amount;

    public Order(String uid, String orderId, double amount) {
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }

    public MsgResult pay(){
        return pay(PayStrategy.DEFAULT_PAY);
    }

    public MsgResult pay(String payKey){
        Payment payment = PayStrategy.get(payKey);
        System.out.println("欢迎使用" + payment.getName());
        System.out.println("本次交易金额为" + amount + ",开始扣款");
        return payment.pay(uid,amount);
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Order order = new Order("1","orderid",324.5);
        System.out.println(order.pay(PayStrategy.UNION_PAY));
    }
}

4、DispatcherServlet的优化

我们都知道SpringMVC的请求都是通过DispatcherServlet的doDispatch方法进行分发的,如果让我们设计,可能会这样实现:

public class DispatcherServlet extends HttpServlet {

	private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

		String uri = request.getRequestURI();
		String mid = request.getParameter("mid");
		if("getMemberById".equals(uri)){
			new MemberController().getMemberById(mid);
		} else if("getOrder".equals(uri)) {
			new OrderController().getOrder();
		}// ...
	}

}

上面的代码扩展性确实不太优雅,我们可以使用策略模式进行优化:

public class DispatcherServlet extends HttpServlet {

    private List<Handler> handlerMapping = new ArrayList<Handler>();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req,resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        String uri = req.getRequestURI();
        // 使用uri匹配handler
        Handler handler = null;
        for (Handler h : handlerMapping) {
            if(uri.equals(h.getUrl())){
                handler = h;
                break;
            }
        }
        // 将具体任务分发给Method
        Object result = null;
        try {
            result = handler.getMethod().invoke(handler.getController());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        
        // 写回结果
        try {
            resp.getWriter().write((String) result);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void init() throws ServletException {
        try {
            
            handlerMapping.add(new Handler().setController(MemberController.class.newInstance())
                            .setMethod(MemberController.class.getMethod("getMemberById", new Class[]{String.class}))
                            .setUrl("/web/getMemberById.json"));
            handlerMapping.add(new Handler().setController(OrderController.class.newInstance())
                            .setMethod(OrderController.class.getMethod("getOrderById", new Class[]{String.class}))
                            .setUrl("/web/getOrderById.json"));
            // ...其他handler
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class Handler {
    private Object controller;
    private Method method;
    private String url;

    public Object getController() {
        return controller;
    }

    public Handler setController(Object controller) {
        this.controller = controller;
        return this;
    }

    public Method getMethod() {
        return method;
    }

    public Handler setMethod(Method method) {
        this.method = method;
        return this;
    }

    public String getUrl() {
        return url;
    }

    public Handler setUrl(String url) {
        this.url = url;
        return this;
    }
}

5、文件排序案例

我们再使用一个文件排序的案例加深策略模式的印象。

有这样一个需求,希望写一个小程序,实现对一个文件进行排序的功能。不同大小的文件排序的算法是不同的,我们初步可能会这样实现:

public class Sorter {
  private static final long GB = 1000 * 1000 * 1000;
  public void sortFile(String filePath) {
    // 省略校验逻辑
    File file = new File(filePath);
    long fileSize = file.length();
    if (fileSize < 6 * GB) { // [0, 6GB)
      quickSort(filePath);
    } else if (fileSize < 10 * GB) { // [6GB, 10GB)
      externalSort(filePath);
    } else if (fileSize < 100 * GB) { // [10GB, 100GB)
      concurrentExternalSort(filePath);
    } else { // [100GB, ~)
      mapreduceSort(filePath);
    }
  }
  private void quickSort(String filePath) {
    // 快速排序
  }
  private void externalSort(String filePath) {
    // 外部排序
  }
  private void concurrentExternalSort(String filePath) {
    // 多线程外部排序
  }
  private void mapreduceSort(String filePath) {
    // 利用MapReduce多机排序
  }
}
public class SortingTool {
  public static void main(String[] args) {
    Sorter sorter = new Sorter();
    sorter.sortFile(args[0]);
  }
}

以上代码并不能体现面向对象的魅力,完全是面向过程的,而我们日常开发中这样的代码也是占大多数。

我们使用策略模式进行重构:

// 策略接口
public interface ISortAlg {
  void sort(String filePath);
}
// 具体策略类
public class QuickSort implements ISortAlg {
  @Override
  public void sort(String filePath) {
    //...
  }
}
public class ExternalSort implements ISortAlg {
  @Override
  public void sort(String filePath) {
    //...
  }
}
public class ConcurrentExternalSort implements ISortAlg {
  @Override
  public void sort(String filePath) {
    //...
  }
}
public class MapReduceSort implements ISortAlg {
  @Override
  public void sort(String filePath) {
    //...
  }
}
// 排序
public class SortAlgFactory {
  private static final Map<String, ISortAlg> algs = new HashMap<>();
  static {
    algs.put("QuickSort", new QuickSort());
    algs.put("ExternalSort", new ExternalSort());
    algs.put("ConcurrentExternalSort", new ConcurrentExternalSort());
    algs.put("MapReduceSort", new MapReduceSort());
  }
  public static ISortAlg getSortAlg(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return algs.get(type);
  }
}
public class Sorter {
  private static final long GB = 1000 * 1000 * 1000;
  public void sortFile(String filePath) {
    // 省略校验逻辑
    File file = new File(filePath);
    long fileSize = file.length();
    ISortAlg sortAlg;
    if (fileSize < 6 * GB) { // [0, 6GB)
      sortAlg = SortAlgFactory.getSortAlg("QuickSort");
    } else if (fileSize < 10 * GB) { // [6GB, 10GB)
      sortAlg = SortAlgFactory.getSortAlg("ExternalSort");
    } else if (fileSize < 100 * GB) { // [10GB, 100GB)
      sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort");
    } else { // [100GB, ~)
      sortAlg = SortAlgFactory.getSortAlg("MapReduceSort");
    }
    sortAlg.sort(filePath);
  }
}

Sorter 类中的 sortFile() 函数还是有一堆 if-else 逻辑。这里的 if-else 逻辑分支不多、也不复杂,这样写完全没问题。但如果你特别想将 if-else 分支判断移除掉,那也是有办法的。我直接给出代码,你一看就能明白。实际上,这也是基于查表法来解决的,其中的“algs”就是“表”。

public class Sorter {
  private static final long GB = 1000 * 1000 * 1000;
  private static final List<AlgRange> algs = new ArrayList<>();
  static {
    algs.add(new AlgRange(0, 6*GB, SortAlgFactory.getSortAlg("QuickSort")));
    algs.add(new AlgRange(6*GB, 10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
    algs.add(new AlgRange(10*GB, 100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")));
    algs.add(new AlgRange(100*GB, Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")));
  }
  public void sortFile(String filePath) {
    // 省略校验逻辑
    File file = new File(filePath);
    long fileSize = file.length();
    ISortAlg sortAlg = null;
    for (AlgRange algRange : algs) {
      if (algRange.inRange(fileSize)) {
        sortAlg = algRange.getAlg();
        break;
      }
    }
    sortAlg.sort(filePath);
  }
  private static class AlgRange {
    private long start;
    private long end;
    private ISortAlg alg;
    public AlgRange(long start, long end, ISortAlg alg) {
      this.start = start;
      this.end = end;
      this.alg = alg;
    }
    public ISortAlg getAlg() {
      return alg;
    }
    public boolean inRange(long size) {
      return size >= start && size < end;
    }
  }
}

三、源码中的策略模式

1、Comparator接口

JDK中一个常用的比较器Comparator接口,有一个常用的方法compare():

public interface Comparator<T> {
	int compare(T o1, T o2);
}

Comparator抽象下面有非常多的实现类,我们经常会把Comparator作为参数传入作为排序策略,例如Arrays类的parallelSort方法等:

public class Arrays{
	public static <T> void sort(T[] a, Comparator<? super T> c) {
		if (c == null) {
		sort(a);
		} else {
		if (LegacyMergeSort.userRequested)
		legacyMergeSort(a, c);
		else
		TimSort.sort(a, 0, a.length, c, null, 0, 0);
		}
	}
}

Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。

public class demo {
	public static void main(String[] args) {
		Integer[] data = {12, 2, 3, 2, 4, 5, 1};
		// 实现降序排序
		Arrays.sort(data, new Comparator<Integer>() {
		public int compare(Integer o1, Integer o2) {
		return o2 - o1;
		}
		});
		System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1]
	}
}

这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用
Comparator子实现类中的 compare() 方法吗?让我们继续查看TimSort类的 sort() 方法,代码如下:

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                     T[] work, int workBase, int workLen) {
    assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

    int nRemaining  = hi - lo;
    if (nRemaining < 2)
        return;  // Arrays of size 0 and 1 are always sorted

    // If array is small, do a "mini-TimSort" with no merges
    if (nRemaining < MIN_MERGE) {
        int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
        binarySort(a, lo, hi, lo + initRunLen, c);
        return;
    }

    /**
     * March over the array once, left to right, finding natural runs,
     * extending short natural runs to minRun elements, and merging runs
     * to maintain stack invariant.
     */
    TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
    int minRun = minRunLength(nRemaining);
    do {
        // Identify next run
        int runLen = countRunAndMakeAscending(a, lo, hi, c);

        // If run is short, extend to min(minRun, nRemaining)
        if (runLen < minRun) {
            int force = nRemaining <= minRun ? nRemaining : minRun;
            binarySort(a, lo, lo + force, lo + runLen, c);
            runLen = force;
        }

        // Push run onto pending-run stack, and maybe merge
        ts.pushRun(lo, runLen);
        ts.mergeCollapse();

        // Advance to find next run
        lo += runLen;
        nRemaining -= runLen;
    } while (nRemaining != 0);

    // Merge all remaining runs to complete sort
    assert lo == hi;
    ts.mergeForceCollapse();
    assert ts.stackSize == 1;
}

上面的代码中最终会跑到 countRunAndMakeAscending() 这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。

2、Spring的InstantiationStrategy

Spring初始化用到的InstantiationStrategy接口:

public interface InstantiationStrategy {

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
			throws BeansException;

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			Constructor<?> ctor, Object... args) throws BeansException;

	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Object factoryBean, Method factoryMethod, Object... args)
			throws BeansException;

}

它有两种策略:CglibSubclassingInstantiationStrategy和SimpleInstantiationStrategy,我们发现了CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy,说明在实际应用中多种策略之间还可以继承使用。我们可以作为一个参考,在实际业务场景中,可以根据需要进行设计。

3、Spring的Resource

package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;

public interface Resource extends InputStreamSource {

	boolean exists();

	default boolean isReadable() {
		return exists();
	}

	default boolean isOpen() {
		return false;
	}

	default boolean isFile() {
		return false;
	}

	URL getURL() throws IOException;

	URI getURI() throws IOException;

	File getFile() throws IOException;

	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	@Nullable
	String getFilename();

	String getDescription();

}

Resource有很多子类:
在这里插入图片描述

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

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

相关文章

在字节做了5年的软件测试,被辞了,太真实了...

先简单说下&#xff0c;涵哥是某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。还差一个月也6年了吧&#xff0c;算是在这行的资深划水员。6年的时间也让涵哥从…

ChatGPT 辅助生成PPT

前言 介绍 ChatGPT 与 MindShow 结合高效生成 PPT。 文章目录 前言一、准备工具二、使用步骤1. 内容生成2. 制作 PPT三、小节一、准备工具 ChatGPT:MindShow:MindShow网站 MindShow 内置了丰富的模板、图表和设计元素。具有自动排版功能,可根据输入内容智能调整布局。二、使…

29岁测试被辞,面试2个月还找不到工作....

最近一个29岁老同学联系我&#xff0c;因为被公司辞退&#xff0c;找我倾诉&#xff0c;于是写下此文。 他是14年二本毕业&#xff0c;在我的印象里人特别懒&#xff0c;不爱学习&#xff0c;专业不好&#xff0c;毕业前因为都没找到合适工作&#xff0c;直接去创业了&#xf…

导入报错:Limits: MIN_INFLATE_RATIO: 0.010000, Entry: xl/drawings/drawing1.xml

今天突然遇到一个现场提出的问题&#xff1a;说导入文件导入不成功&#xff0c;咋突然有这个问题 2023-05-23 14:19:11,174 ERROR [http-nio-9104-exec-5] o.a.c.c.C.[.[.[.[dispatcherServlet] [DirectJDKLog.java : 175] Servlet.service() for servlet [dispatcherServlet]…

VMwareESXI虚拟机黑群晖7.2 正式版 (懒人包)

版本说明&#xff1a; VMware Workstation 桌面版虚拟机&#xff0c;可下载VMware专用版本 VMware ESXi虚拟机&#xff0c;可以下载OVA版本 VMware Workstation桌面版虚拟机 使用教程&#xff1a; 1.下载VMware专用版本&#xff0c;然后进行解压&#xff0c;双击解压出来的&q…

前端Vue:权限管理,给角色分配权限

&#x1f449;前端-Vue权限控制&#xff0c;菜单权限&#xff0c;按钮权限_一人创客的博客-CSDN博客 目录 介绍&#xff1a; 前端权限的概念&#xff1a; 前端权限的意义&#xff1a; Vue权限管理的代码实现&#xff1a; 菜单 刷新界⾯菜单消失 标识⽤户名, ⽅便查看当前…

chatgpt赋能Python-python_kali

Python与Kali Linux&#xff1a;SEO攻击的更有效方法 介绍 Python是一种强大且高度灵活的编程语言&#xff0c;也是许多安全专家、黑客和网络攻击者所喜欢的工具之一。Kali Linux是一个专注于安全审计、渗透测试和网络安全的Linux发行版&#xff0c;其中包含了许多流行的安全…

微服务开发系列 第四篇:分页查询

总概 A、技术栈 开发语言&#xff1a;Java 1.8数据库&#xff1a;MySQL、Redis、MongoDB、Elasticsearch微服务框架&#xff1a;Spring Cloud Alibaba微服务网关&#xff1a;Spring Cloud Gateway服务注册和配置中心&#xff1a;Nacos分布式事务&#xff1a;Seata链路追踪框架…

Chat GPT 教您如何发现和处理无效数据

Chat GPT 教您如何发现和处理无效数据 在进行数据管理时&#xff0c;无论是数据分析、数据挖掘还是机器学习项目&#xff0c;无效数据都可能对结果造成严重的影响。因此&#xff0c;发现和处理无效数据变得至关重要。本文将从如何处理无效数据的角度&#xff0c;详细探讨数据清…

数据结构与算法(五)

哈希表&#xff08;hash&#xff09; 什么是hash&#xff1f; 散列,是把任意长度的输入通过散列算法变换成固定长度输出&#xff0c;该输出的值就是散列值。这种转换是一种压缩映射。映射表达的是一一对应的关系&#xff0c;也就是说&#xff0c;散列值的空间通常会小于输入空…

[算法前沿]--014-DeepSpeed-Chat 模型训练实战<下>

文章目录 1.实战Step1:监督微调1.1 任务说明: 使用标定的数据对预训练模型进行微调评价与测试:2 实战Step2:Reward模型微调3.实战Step3:RLHF训练3.评价与测试4.QA参考1.实战Step1:监督微调 基础语言模型是指只在大规模文本语料中进行了预训练的模型,未经过指令和下游任务…

淘宝商品历史价格API接口 调用说明及功能介绍

淘宝商品历史价格API是一款可以帮助用户获取淘宝商品历史价格数据的接口。通过该接口&#xff0c;用户可以轻松地获取某个商品在过去一段时间中的价格趋势和波动情况&#xff0c;以便更好地了解该商品的市场走势和价值变化情况。 该API具备以下功能&#xff1a; 1. 支持多种查…

对于大流量请求的处理方案(NATNginx)

情况描述&#xff1a; 如图所示&#xff0c;厂家的A服务器&#xff0c;到客户的C服务器不通&#xff0c;需要我这边通过B服务器做一次流量转发。 由于&#xff0c;每次请求数据流都太大&#xff0c;怕HTTPS方式&#xff0c;会出现请求超时&#xff0c;断开连接。 解决方案&am…

什么是自动化测试框架?我们该如何搭建自动化测试框架?

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。之前学习自动化测试的过程中&#xff0c;一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上自己的一些实践&#xff0c;算是对“框架”…

Codeium:一个免费的、支持70多种编程语言的、可以与你对话的智能编程助手,让你从繁琐的代码中解放出来

摘要 Codeium&#xff1a;免费的人工智能代码加速工具&#xff0c;让编程变得更简单、更快、更有趣 如何使用Codeium来提高编程效率和质量&#xff1f;一篇文章教你掌握Codeium的三大功能&#xff1a;代码完成、聊天和搜索 Codeium vs GitHub Copilot&#xff1a;哪个更适合你…

Linux的软件生态与两个方面,客户端/Linux软件下载安装的认识,yum源/仓库(repo)与yum指令的本质,yum指令操作等

铺垫1&#xff1a;服务器属于硬件 服务器是一种计算机硬件设备&#xff0c;主要用于存储、管理和处理数据以及为其他计算机提供服务。服务器通常具有高性能的处理器、大容量的硬盘、大内存和高速网络连接等特点&#xff0c;可以提供各种服务&#xff0c;如网站托管、电子邮件服…

【教程】对视频平台授权时,加密机设备如何固定IP?

我们在此前的文章中也介绍过&#xff0c;我们的视频平台都是通过加密机、加密狗、激活码三种方式进行服务授权的&#xff0c;其中&#xff0c;加密机使用得较多。具体注意事项可以查看这篇文章&#xff1a;加密机授权注意事项汇总及解决方法。 加密机在使用时&#xff0c;需要在…

bat操作git(一键提交)

添加环境变量&#xff1a;D:\Git\Git\cmd 环境变量添加完毕后就可以直接在命令框使用git命令了 脚本实现 实现一键完成远程仓库的更新 echo off git add . git commit -m "daily push data-structure-and-algorithms" git push echo push respostory successful…

python包之matplotlib基础概念和代码详解

1 基础概念 Figure&#xff1a; 可以理解为 canvas(画布)&#xff0c;在画布上可以展示一个或多个Axes Axes&#xff1a;中文翻译为轴&#xff0c;但与数学中的概念不同&#xff0c;Axes可以理解为子画布&#xff0c;它属于Figure。也可以理解为它就是一个图形或绘制图形的区…

为什么有了IP地址,还需要MAC地址呢?

不知道大家有没有困惑&#xff1a;为什么有了IP地址&#xff0c;还需要MAC地址呢&#xff1f;他们之间到底有什么联系&#xff1f;又有什么区别&#xff1f;是不是有一个是多余的&#xff1f; 流言传到了“IP地址”和“MAC地址”的耳朵里&#xff0c;他俩也非常苦恼&#xff0c…