【设计模式】【行为型模式】策略模式(Strategy)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘

🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

什么是策略模式?

策略模式是一种行为设计模式,允许在运行时选择算法或行为。它将算法封装在独立的类中,使得它们可以互换,而不影响客户端代码。

为什么需要策略模式?

策略模式的主要目的是解决算法或行为在代码中硬编码的问题,使得系统更加灵活、可扩展和易于维护。可以优化大量的if-else。

怎样实现策略模式?

策略模式的主要角色如下:

抽象策略(Strategy)类 :这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略所需的接口。

具体策略(Concrete Strategy)类 :实现了抽象策略定义的接口,提供具体的算法实现或行为

环境(Context)类:持有一个策略类的引用,最终给客户端调用。

【案例】促销活动

百货公司,在不同的节日,会有不同的促销

定义百货公司所有促销活动的共同接口

java 复制代码
public interface Strategy {
  void show();
}

定义具体策略角色(Concrete Strategy):每个节日具体的促销活动

java 复制代码
//为春节准备的促销活动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();           
 }                     
} 

二、策略模式在源码中的运用

2.1、Java Collections 中的排序策略

Java的Collections.sort()方法使用了策略模式来实现排序功能。它允许通过传递不同的Comparator实现来定义不同的排序策略。

java 复制代码
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);

// 策略1:升序排序
Collections.sort(numbers, new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a.compareTo(b);
    }
});
System.out.println("升序排序: " + numbers);

// 策略2:降序排序
Collections.sort(numbers, new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return b.compareTo(a);
    }
});
System.out.println("降序排序: " + numbers);

在上面的代码中Comparator是策略接口。具体的排序逻辑(升序、降序)是策略实现。Collections.sort()是上下文,负责调用策略。

源码中,Collections类的sort方法,里面调用了Arrays类的sort方法。ArraysTimSort类的sort方法

java 复制代码
// Collections类
public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);    // 调用Arrays类的sort方法
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

// 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); // 调用TimSort类的sort方法
    }
}

TimSort类中,这里我们只要关注,我们传的策略,入参c的使用地方就好了。这里会调用countRunAndMakeAscending方法,我们关注这个方法,我们传的排序策略,也就是入参c,会被使用。

java 复制代码
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);  // 关注调用这个方法
...


// countRunAndMakeAscending 方法
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                Comparator<? super T> c) {
    assert lo < hi;
    int runHi = lo + 1;
    if (runHi == hi)
        return 1;

    if (c.compare(a[runHi++], a[lo]) < 0) { // 排序策略被使用
        while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
            runHi++;
        reverseRange(a, lo, runHi);
    } else {                          
        while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
            runHi++;
    }

    return runHi - lo;
}

2.2、Spring 中的资源加载策略

Spring框架中的ResourceLoader接口和其实现类(如ClassPathResourceLoaderFileSystemResourceLoader等)也使用了策略模式。

java 复制代码
ResourceLoader resourceLoader = new DefaultResourceLoader();

// 策略1:从类路径加载资源
Resource classPathResource = resourceLoader.getResource("classpath:application.properties");
System.out.println("类路径资源: " + classPathResource.exists());

// 策略2:从文件系统加载资源
Resource fileSystemResource = resourceLoader.getResource("file:/path/to/file.txt");
System.out.println("文件系统资源: " + fileSystemResource.exists());

ResourceLoader是策略接口。具体的资源加载逻辑(类路径、文件系统等)是策略实现。DefaultResourceLoader是上下文,负责调用策略。

下面是结合源码说明
策略接口:ResourceLoader 接口

java 复制代码
public interface ResourceLoader {
    Resource getResource(String location);
}

具体策略:ClassPathResource、FileSystemResource等是具体的策略实现。

java 复制代码
// ClassPathResource实现
public class ClassPathResource extends AbstractFileResolvingResource {
    private final String path;
    public ClassPathResource(String path) {
        this.path = path;
    }
    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is = getClassLoader().getResourceAsStream(path);
        if (is == null) {
            throw new FileNotFoundException("Resource not found: " + path);
        }
        return is;
    }
}

// FileSystemResource
public class FileSystemResource extends AbstractResource {
    private final File file;
    public FileSystemResource(String path) {
        this.file = new File(path);
    }
    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(file);
    }
}

上下文:DefaultResourceLoader 是上下文,负责根据路径选择合适的策略。

java 复制代码
public class DefaultResourceLoader implements ResourceLoader {
    @Override
    public Resource getResource(String location) {
        // 根据路径前缀选择策略
        if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
        }
        else if (location.startsWith(FILE_URL_PREFIX)) {
            return new FileSystemResource(location.substring(FILE_URL_PREFIX.length()));
        }
        else {
            try {
                // 尝试作为URL加载
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // 如果都不是,默认为文件系统资源
                return new FileSystemResource(location);
            }
        }
    }
}

这里应该有UU们会好奇,诶,为什么具体策略没有实现策略接口 ?Spring 的资源加载策略是策略模式的一种变体。它与经典策略模式的区别在于:

  • 经典策略模式:
    • 策略接口和具体策略是直接实现的。
    • 例如,排序策略中,Comparator 是策略接口,具体的排序类是策略实现。
  • Spring 的资源加载策略:
    • 策略接口(ResourceLoader)和具体策略(ClassPathResource 等)之间通过上下文(DefaultResourceLoader)连接。
    • 具体策略实现的是 Resource 接口,而不是 ResourceLoader 接口。

三、总结

策略模式通过将算法或行为封装到独立的类中,提供了一种灵活、可扩展的方式来管理代码中的变化部分。它的核心优势是解耦动态切换,但也会带来类的数量增加和客户端使用成本的问题。适用于需要动态切换行为、避免重复代码或隔离算法实现细节的场景。

优点

灵活性 :允许在运行时动态切换算法或行为,无需修改客户端代码。

可扩展性 :新增策略时只需添加新的策略类,符合开闭原则(对扩展开放,对修改关闭)。

解耦 :将算法或行为与使用它的上下文分离,降低了代码的耦合度。

避免重复代码 :将相似的算法提取到独立的策略类中,减少代码重复。

易于测试:每个策略类可以独立测试,简化了测试过程。

缺点

增加类的数量 :每个策略都需要一个独立的类,可能会导致类的数量增多,增加系统复杂性。

客户端需要了解策略 :客户端必须知道有哪些策略,并选择合适的策略,增加了使用成本。

性能开销:在运行时切换策略可能会引入额外的性能开销(如对象创建和销毁)

适用场景

需要动态切换算法或行为 :例如,支付方式、排序算法、资源加载策略等。

有多个相似的类,只有行为不同 :例如,不同类型的折扣计算、不同的日志记录方式等。

避免使用复杂的条件语句 :当代码中有大量if-else或switch-case语句时,可以用策略模式替代。

需要隔离算法的实现细节 :当不希望暴露算法的实现细节,或者希望算法可以独立变化时。

需要对算法进行扩展:当系统需要支持新的算法,且不希望修改现有代码时。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili

相关推荐
martian6655 分钟前
【Java基础篇】——第4篇:Java常用类库与工具类
java·开发语言
violin-wang30 分钟前
Intellij IDEA调整栈内存空间大小详细教程,添加参数-Xss....
java·ide·intellij-idea·xss·栈内存·栈空间
在下陈平安31 分钟前
java-LinkedList源码详解
java·开发语言
黑客老李34 分钟前
一次使用十六进制溢出绕过 WAF实现XSS的经历
java·运维·服务器·前端·sql·学习·xss
Mao.O1 小时前
IDEA编写SpringBoot项目时使用Lombok报错“找不到符号”的原因和解决
java·spring boot·intellij-idea·lombok
muxue1782 小时前
数据结构:栈
java·开发语言·数据结构
R-sz5 小时前
bladeX微服务框架如何修改nacos分组
java·微服务·架构
众乐乐_20086 小时前
mybatis 是否支持延迟加载?延迟加载的原理是什么?
java·servlet·tomcat
you来有去6 小时前
idea启动报错# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffccf76e433
java·ide·intellij-idea
幽默小吴7 小时前
Spring Boot常见面试题总结
java·spring boot·后端