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

简介: 一、入门 什么是策略模式? 策略模式是一种行为设计模式,允许在运行时选择算法或行为。它将算法封装在独立的类中,使得它们可以互换,而不影响客户端代码。 为什么需要策略模式? 策略模式的主要目的是解决算法

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

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

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

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

一、入门

什么是策略模式?

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

为什么需要策略模式?

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

怎样实现策略模式?

策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为
环境(Context)类:持有一个策略类的引用,最终给客户端调用。

【案例】促销活动
百货公司,在不同的节日,会有不同的促销
image.png

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

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();           
 }                     
}

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

2.1、Java Collections 中的排序策略

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

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方法

// 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,会被使用。

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等)也使用了策略模式。

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 接口

public interface ResourceLoader {
   
    Resource getResource(String location);
}

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

// 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 是上下文,负责根据路径选择合适的策略。

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

目录
相关文章
|
21天前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
216 35
|
6月前
|
设计模式 网络协议 Java
【设计模式】【行为型模式】状态模式(State)
一、入门 什么是状态模式? 状态模式(State Pattern)是一种行为设计模式,允许对象在其内部状态改变时改变其行为,使其看起来像是改变了类。状态模式的核心思想是将对象的状态封装成独立的类,并将
271 16
|
6月前
|
设计模式 算法 前端开发
【设计模式】【行为型模式】职责链模式(Chain of Responsibility)
一、入门 什么是职责链模式? 职责链模式是一种行为设计模式,它允许你将请求沿着一条链传递,直到有对象处理它为止。每个对象都有机会处理请求,或者将其传递给链中的下一个对象。 为什么需要职责链模式? 使用
225 16
|
6月前
|
设计模式 存储 Java
【设计模式】【行为型模式】备忘录模式(Memento)
一、入门 什么是备忘录模式? 备忘录模式(Memento Pattern)是一种行为设计模式,用于在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在需要时恢复该状态。它通常用于实现撤销操作
207 8
|
2月前
|
设计模式 人工智能 算法
基于多设计模式的状态扭转设计:策略模式与责任链模式的实战应用
接下来,我会结合实战案例,聊聊如何用「策略模式 + 责任链模式」构建灵活可扩展的状态引擎,让抽奖系统的状态管理从「混乱战场」变成「有序流水线」。
|
6月前
|
设计模式 算法 Java
设计模式觉醒系列(04)策略模式|简单工厂模式的升级版
本文介绍了简单工厂模式与策略模式的概念及其融合实践。简单工厂模式用于对象创建,通过隐藏实现细节简化代码;策略模式关注行为封装与切换,支持动态替换算法,增强灵活性。两者结合形成“策略工厂”,既简化对象创建又保持低耦合。文章通过支付案例演示了模式的应用,并强调实际开发中应根据需求选择合适的设计模式,避免生搬硬套。最后推荐了JVM调优、并发编程等技术专题,助力开发者提升技能。
|
6月前
|
设计模式 消息中间件 Java
【设计模式】【行为型模式】命令模式(Command)
一、入门 什么是命令模式? 命令模式是一种行为设计模式,它将请求或操作封装为对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录、撤销等操作。 命令模式的核心是将“请求”封装为独立的
215 15
|
6月前
|
设计模式 Java 编译器
【设计模式】【行为型模式】解释器模式(Interpreter)
一、入门 什么是解释器模式? 解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义语言的语法表示,并提供一个解释器来处理该语法。它通常用于需要解释和执行特定语言或表达式的场
136 11
|
6月前
|
设计模式 存储 JavaScript
【设计模式】【行为型模式】迭代器模式(Iterator)
一、入门 什么是迭代器模式? 迭代器模式(Iterator Pattern)是一种行为设计模式,它提供了一种顺序访问聚合对象中元素的方法,而不需要暴露其底层表示。迭代器模式将遍历逻辑从聚合对象中分离出
153 11
|
6月前
|
设计模式 XML JSON
【设计模式】【行为型模式】访问者模式(Visitor)
一、入门 什么是访问者模式? 访问者模式(Visitor Pattern)是一种行为设计模式,允许你将算法与对象结构分离。通过这种方式,可以在不改变对象结构的情况下,向对象结构中的元素添加新的操作。
210 10

热门文章

最新文章