• Java策略模式源码剖析及使用场景


    一、介绍

    策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。

    策略模式的主要角色有

    1. Context(上下文): 负责维护一个具体的策略类实例,并根据它来协调算法的执行。

    2. Strategy(抽象策略): 定义了一个算法族,声明了一个算法的操作,它有一个或多个具体的策略类实现。

    3. ConcreteStrategy(具体策略): 具体实现了 Strategy 接口定义的算法。

    策略模式的优点

    1. 算法可以在运行时进行切换,避免了使用复杂的条件语句。
    2. 扩展性良好,增加一个策略只需增加一个具体的策略类即可。
    3. 避免使用多重条件转移语句,利于代码的维护。

    策略模式的缺点

    1. 客户端必须知道所有的策略类,并且需要理解具体的策略类,违反了最少知识原则。
    2. 增加了对象的数目。

    二、不同的支付方式采用不同的策略

    // 支付策略接口
    interface PaymentStrategy {
        boolean pay(double amount);
    }
    
    // 具体的支付策略实现
    class CashPaymentStrategy implements PaymentStrategy {
        @Override
        public boolean pay(double amount) {
            System.out.println("现金支付了 " + amount + " 元");
            return true;
        }
    }
    
    class CreditCardPaymentStrategy implements PaymentStrategy {
        @Override
        public boolean pay(double amount) {
            System.out.println("信用卡支付了 " + amount + " 元");
            return true;
        }
    }
    
    // 上下文类
    class PaymentContext {
        private PaymentStrategy strategy;
    
        public void setStrategy(PaymentStrategy strategy) {
            this.strategy = strategy;
        }
    
        public boolean executePayment(double amount) {
            return strategy.pay(amount);
        }
    }
    
    // 客户端代码
    public class Client {
        public static void main(String[] args) {
            PaymentContext context = new PaymentContext();
    
            context.setStrategy(new CashPaymentStrategy());
            context.executePayment(100.0); // 现金支付了 100.0 元
    
            context.setStrategy(new CreditCardPaymentStrategy());
            context.executePayment(200.0); // 信用卡支付了 200.0 元
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • PaymentStrategy 接口定义了支付操作的抽象策略。
    • CashPaymentStrategyCreditCardPaymentStrategy 分别实现了现金支付和信用卡支付的具体策略。
    • PaymentContext 类是上下文,它维护了一个策略实例,并提供了执行支付操作的方法。
    • 在客户端代码中,我们可以根据需要动态地设置不同的支付策略,并执行相应的支付操作。
      Java项目中有很多地方都可以使用策略模式,下面我给出一些典型的例子:

    三、 电商定价策略

    在一个电商项目中,我们可以根据不同的营销策略对商品进行不同的定价。比如,对于普通用户可以按照原价定价,而对于VIP用户则可以打折。我们可以使用策略模式来实现这个需求。

    // 定价策略接口
    interface PricingStrategy {
        double calculatePrice(double originalPrice);
    }
    
    // 原价定价策略
    class OriginalPricingStrategy implements PricingStrategy {
        @Override
        public double calculatePrice(double originalPrice) {
            return originalPrice;
        }
    }
    
    // VIP折扣策略
    class VipDiscountPricingStrategy implements PricingStrategy {
        private final double discountRate;
    
        public VipDiscountPricingStrategy(double discountRate) {
            this.discountRate = discountRate;
        }
    
        @Override
        public double calculatePrice(double originalPrice) {
            return originalPrice * (1 - discountRate);
        }
    }
    
    // 定价上下文
    class PricingContext {
        private PricingStrategy strategy;
    
        public void setStrategy(PricingStrategy strategy) {
            this.strategy = strategy;
        }
    
        public double calculatePrice(double originalPrice) {
            return strategy.calculatePrice(originalPrice);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    四、日志记录策略

    在一个需要记录日志的系统中,我们可能需要根据不同的日志级别采用不同的日志记录策略。比如,对于DEBUG级别的日志,我们可以将其记录到控制台,而对于ERROR级别的日志,我们则需要将其记录到文件中。这种情况下,就可以使用策略模式。

    // 日志记录策略接口
    interface LoggingStrategy {
        void log(String message, LogLevel level);
    }
    
    // 控制台日志记录策略
    class ConsoleLoggingStrategy implements LoggingStrategy {
        @Override
        public void log(String message, LogLevel level) {
            System.out.println("[" + level + "] " + message);
        }
    }
    
    // 文件日志记录策略
    class FileLoggingStrategy implements LoggingStrategy {
        @Override
        public void log(String message, LogLevel level) {
            // 将日志记录到文件中
        }
    }
    
    // 日志记录上下文
    class LoggingContext {
        private LoggingStrategy strategy;
    
        public void setStrategy(LoggingStrategy strategy) {
            this.strategy = strategy;
        }
    
        public void log(String message, LogLevel level) {
            strategy.log(message, level);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    五、 压缩算法

    在一个需要进行数据压缩的系统中,我们可能需要根据不同的场景采用不同的压缩算法。比如,对于一般的文本数据,我们可以使用Gzip算法进行压缩,而对于多媒体数据,我们则需要使用更高效的算法。这种情况下,我们可以使用策略模式来实现不同的压缩算法。

    // 压缩策略接口
    interface CompressionStrategy {
        byte[] compress(byte[] data);
        byte[] decompress(byte[] compressedData);
    }
    
    // Gzip压缩策略
    class GzipCompressionStrategy implements CompressionStrategy {
        @Override
        public byte[] compress(byte[] data) {
            // 使用Gzip算法进行压缩
        }
    
        @Override
        public byte[] decompress(byte[] compressedData) {
            // 使用Gzip算法进行解压缩
        }
    }
    
    // ZLIB压缩策略
    class ZlibCompressionStrategy implements CompressionStrategy {
        @Override
        public byte[] compress(byte[] data) {
            // 使用ZLIB算法进行压缩
        }
    
        @Override
        public byte[] decompress(byte[] compressedData) {
            // 使用ZLIB算法进行解压缩
        }
    }
    
    // 压缩上下文
    class CompressionContext {
        private CompressionStrategy strategy;
    
        public void setStrategy(CompressionStrategy strategy) {
            this.strategy = strategy;
        }
    
        public byte[] compress(byte[] data) {
            return strategy.compress(data);
        }
    
        public byte[] decompress(byte[] compressedData) {
            return strategy.decompress(compressedData);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    六、Java 中的 Arrays.sort() 方法,不同的排序策略进行排序

    Java 的 Arrays.sort() 方法在内部确实使用了策略模式来实现不同的排序算法。下面我们来详细分析一下它的底层实现源码。

    Arrays.sort() 方法的实现位于 java.util.Arrays 类中,它根据待排序数组的类型和大小,选择合适的排序算法进行排序。在这个过程中,它使用了策略模式来封装不同的排序算法。

    首先,让我们看看 Arrays.sort() 方法的签名:

    public static void sort(Object[] a) {
        // 判断数组类型和长度
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComputeMaxs.parallelSort(a, null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看到,对于对象数组,sort() 方法会根据一个名为 LegacyMergeSort.userRequested 的标志位,选择使用旧的归并排序算法(legacyMergeSort)或者新的并行排序算法(ComputeMaxs.parallelSort)。这里就体现了策略模式的思想,不同的排序算法被封装在不同的策略类中。

    接下来,我们看看新的并行排序算法 ComputeMaxs.parallelSort() 的实现:

    static <T extends Comparable<? super T>> void parallelSort(T[] a, Comparator<? super T> cmp) {
        // 根据数组长度选择合适的排序算法
        int len = a.length;
        if (len > MIN_ARRAY_SORT_GRAN) {
            rangeSort(a, 0, len - 1, cmp);
        } else if (len != 0) {
            Binarysort.sort(a, 0, len, null, cmp);
        }
    }
    
    private static <T extends Comparable<? super T>>
    void rangeSort(T[] a, int from, int to, Comparator<? super T> cmp) {
        // 使用TimSort或归并排序算法进行排序
        if (from == 0 && to == a.length - 1) {
            // 使用TimSort算法
            new TimSort(a, cmp).sort(a, from, to);
        } else {
            // 使用归并排序算法
            new MergeSort(a, cmp, from, to).sort();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在上面的代码中,我们可以看到

    1. parallelSort() 方法根据数组长度选择使用 rangeSort()Binarysort.sort()rangeSort() 用于较大的数组,而 Binarysort.sort() 用于较小的数组。
    2. rangeSort() 方法根据数组的范围,选择使用 TimSort 算法或归并排序(MergeSort)算法。

    这里,TimSortMergeSortBinarysort 就是不同的排序策略类。它们都实现了相应的排序算法,而 parallelSort()rangeSort() 充当了策略模式中的上下文(Context)角色,根据具体情况选择合适的策略类。

    让我们继续看看 TimSort 的实现

    static final class TimSort<T extends Comparable<? super T>> extends MergeSort<T> {
        // TimSort算法的实现代码...
    }
    
    • 1
    • 2
    • 3

    TimSort 类继承自 MergeSort 类,它是一种改进的归并排序算法,对于部分有序的数组有更好的性能表现。

    MergeSort 类则实现了传统的归并排序算法

    static final class MergeSort<T extends Comparable<? super T>> extends Sorter<T> {
        // 归并排序算法的实现代码...
    }
    
    • 1
    • 2
    • 3

    MergeSort 继承自 Sorter 抽象类,Sorter 定义了一些公共的排序方法和字段,同时也包含了 Binarysort 的实现。

    abstract static class Sorter<T extends Comparable<? super T>> {
        // 一些公共方法和字段...
    
        // Binarysort算法的实现
        static <T extends Comparable<? super T>> void sort(T[] a, int from, int to, ...) {
            // Binarysort算法实现代码...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通过上面的源码分析,我们可以看到 Java Arrays.sort() 方法是如何使用策略模式来选择合适的排序算法的。不同的排序算法被封装在不同的策略类中,如 TimSortMergeSortBinarysort。而 parallelSort()rangeSort() 方法则根据具体情况选择合适的策略类进行排序。

    这种设计使得 Arrays.sort() 方法可以灵活地切换不同的排序算法,也便于后续添加新的排序算法。同时,由于每种算法都被封装在单独的类中,代码的可读性和维护性也得到了提高。

    总的来说,Java 的 Arrays.sort() 方法是一个很好的策略模式的应用实例,它展示了如何使用策略模式来封装和选择不同的算法,提高代码的灵活性和可扩展性。

    七、Spring 中的 ResourceLoader 类,不同的资源位置采用不同的加载策略

    在 Spring 5 中,ResourceLoader 接口使用了策略模式来加载不同类型的资源。它定义了一个统一的接口,而具体的资源加载策略则由不同的实现类来处理。下面我们来分析一下它的底层源码:

    ResourceLoader 接口的定义如下:

    public interface ResourceLoader {
        Resource getResource(String location);
        ClassLoader getClassLoader();
    }
    
    • 1
    • 2
    • 3
    • 4

    它定义了两个方法:

    1. getResource(String location) 用于根据给定的资源位置返回对应的 Resource 对象。
    2. getClassLoader() 返回相关的 ClassLoader

    Spring 提供了一个默认的 ResourceLoader 实现类 DefaultResourceLoader。它实现了多种资源加载策略,包括从类路径、文件系统、URL 等不同位置加载资源。

    public class DefaultResourceLoader implements ResourceLoader {
        // ...
    
        @Override
        public Resource getResource(String location) {
            Assert.notNull(location, "Location must not be null");
    
            // 通过 ClassPathContextResource 策略加载类路径资源
            if (location.startsWith(CLASSPATH_URL_PREFIX)) {
                return new ClassPathContextResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
            }
            // ... 其他策略
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    getResource 方法中,根据资源位置的不同前缀,采用不同的资源加载策略。比如,对于以 classpath: 开头的资源位置,它会使用 ClassPathContextResource 策略进行加载。

    ClassPathContextResourceClassPathResource 的子类,它实现了从类路径中加载资源的具体策略:

    public class ClassPathResource extends AbstractFileResolvingResource {
        // 获取资源的具体实现...
    }
    
    • 1
    • 2
    • 3

    Spring 还提供了其他一些 ResourceLoader 的实现,如:

    • FileSystemResourceLoader: 从文件系统中加载资源
    • ServletContextResourceLoader: 从 Servlet 上下文中加载资源
    • ...

    通过组合不同的资源加载策略,Spring 可以非常灵活地从各种不同的位置加载资源。

    总的来说,ResourceLoader 接口充当了策略模式中的抽象策略角色,而具体的资源加载策略则由其不同的实现类(如 ClassPathResourceFileSystemResource 等)扮演了具体策略的角色。当需要加载资源时,Spring 会根据资源位置信息选择合适的策略实现,从而完成资源的加载过程。

  • 相关阅读:
    SPARQL基础入门练习
    工厂方法(Factory Methods),抽象工厂(Abstract Factory )
    Windows线程 信号量 CreateSemaphore创建信号量、RelaseSemaphore设置信号量
    java计算机毕业设计客服管理系统源码+mysql数据库+系统+lw文档+部署
    Java使用Validator类进行实体属性格式验证(一次性返回所有验证结果)
    MySQL索引
    Linux高性能服务器编程——ch5笔记
    【Python&语义分割】Segment Anything(SAM)模型交互式分割+掩膜保存(三)
    MAC 安装 maven
    一、网络基础知识
  • 原文地址:https://blog.csdn.net/weixin_44716935/article/details/136630610