23种设计模式之策略模式

策略模式详解:模式简介、动机、结构及应用

  • 一、策略模式简介
    • [1.1 定义](#1.1 定义)
    • [1.2 模式类型](#1.2 模式类型)
    • [1.3 主要作用](#1.3 主要作用)
    • [1.4 优点](#1.4 优点)
    • [1.5 缺点](#1.5 缺点)
  • 二、模式动机
  • 三、模式结构
  • 四、策略模式的实现
    • [4.1 步骤一:定义策略接口](#4.1 步骤一:定义策略接口)
    • [4.2 步骤二:实现具体策略类](#4.2 步骤二:实现具体策略类)
    • [4.3 步骤三:创建上下文类](#4.3 步骤三:创建上下文类)
    • [4.4 步骤四:客户端切换策略](#4.4 步骤四:客户端切换策略)
    • [4.5 动态设置策略](#4.5 动态设置策略)
  • 五、应用场景
    • [5.1 排序算法](#5.1 排序算法)
    • [5.2 支付方式](#5.2 支付方式)
    • [5.3 数据压缩](#5.3 数据压缩)
    • [5.4 游戏AI行为](#5.4 游戏AI行为)
  • 六、策略模式的变体与扩展
    • [6.1 内联策略](#6.1 内联策略)
    • [6.2 参数化策略](#6.2 参数化策略)
    • [6.3 缓存策略](#6.3 缓存策略)
  • 七、总结:rocket::rocket::rocket:

一、策略模式简介

1.1 定义


策略模式,又称政策模式,是行为型设计模式之一。其定义如下:

定义一系列算法,把它们一个个封装起来,并且使它们互相替换。本模式使得算法可独立于使用它的客户而变化

1.2 模式类型


行为型

1.3 主要作用


  • 封装算法:将每一个算法封装成对象,使得算法可以互相替换和独立扩展。
  • 消除条件分支语句:避免在客户端代码中使用大量的条件分支语句。通过策略模式,条件分支语句被算法的多态性替代。
  • 行为的独立变化:可以在不影响客户端的情况下,增加新的算法或修改已有算法,增强系统的灵活性和可扩展性。

1.4 优点


  • 开放-关闭原则:策略模式通过策略接口的多态性实现,符合开放-封闭原则,可以在不修改客户端代码的情况下,增加新的策略。
  • 消除条件语句:避免了在客户端代码中使用大量的条件语句,将算法的选择委托给策略对象,更加清晰、简介。
  • 提高可扩展性:通过为不同算法定义独特的策略类,可以方便地扩展新的算法而不会影响现有系统。
  • 更好的代码组织:算法的实现被封装在独立的策略类中,代码组织更好,易于理解和维护。

1.5 缺点


  • 略微增加复杂性:由于引入了策略接口和具体的策略类,增加了类的数量和系统的复杂性。
  • 客户端的意识增强:客户端必须了解所有的策略类,并知道在何种情况下使用哪一个策略,增加客户端的负担。

二、模式动机


在实际开发中,常常遇到对某种行为或算法进行替换或扩展的需求。例如,一个图形界面程序需要对不同的绘图算法进行切换、一个电商系统需要支持多种不同的支付方式、游戏开发中需要不同的AI行为策略等等。对于这些需求,通常的做法是使用大量的条件分支语句来实现不同的行为:

java 复制代码
if (condition1) {
    // Algorithm A
} else if (condition2) {
    // Algorithm B
} else if (condition3) {
    // Algorithm C
}

然而,随着需求的不断增加,这种实现方式会导致代码冗长复杂且不易维护 ,违背了开闭原则。策略模式的动机就是为了解决这一问题,通过将算法封装为独立的策略类 ,实现算法的易扩展高可维护性

三、模式结构


策略模式的关键在于:算法被分离为独立的策略类,各类策略实现一个共同的策略接口,这个接口可以故定义策略的各种行为方法。在上下文环境(Context)类中持有策略接口的引用,客户通过设置具体的策略对象来进行算法的切换。

  • Context(上下文):持有一个策略对象的引用,它会把客户端的请求委托给策略对象。
  • Strategy(抽象策略):策略的共同接口或抽象类。
  • ConcreteStrategy(具体策略):具体实现了某种算法的策略类。

Java代码示例

以下是策略模式的示例代码,用于展示策略模式在现实场景中的应用。

java 复制代码
// 策略接口
interface Strategy {
    void executeAlgorithm();
}

// 具体策略类A
class ConcreteStrategyA implements Strategy {
    public void executeAlgorithm() {
        System.out.println("执行策略A算法");
    }
}

// 具体策略类B
class ConcreteStrategyB implements Strategy {
    public void executeAlgorithm() {
        System.out.println("执行策略B算法");
    }
}

// 上下文类
class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.executeAlgorithm();
    }
}

// 客户端代码
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        
        // 设置为策略A
        context.setStrategy(new ConcreteStrategyA());
        context.executeStrategy();
        
        // 设置为策略B
        context.setStrategy(new ConcreteStrategyB());
        context.executeStrategy();
    }
}

在上述代码中,我们定义了一个策略接口和两个具体策略类,通过在上下文类中设置具体的策略对象,客户端可以方便地切换不同的算法。

四、策略模式的实现

4.1 步骤一:定义策略接口


策略接口是所有策略类的共同父接口,它定义了所有策略类必须实现的方法。以下是策略接口的定义:

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

4.2 步骤二:实现具体策略类


每一个具体策略类都实现了策略接口,并包含了具体的算法实现。以下是两个具体策略类的实现:

java 复制代码
class ConcreteStrategyA implements Strategy {
    public void executeAlgorithm() {
        System.out.println("执行策略A算法");
    }
}
class ConcreteStrategyB implements Strategy {
    public void executeAlgorithm() {
        System.out.println("执行策略B算法");
    }
}

4.3 步骤三:创建上下文类


上下文类持有一个策略对象引用,并通过该引用来调用具体的算法。以下是上下文类的定义和实现:

java 复制代码
class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.executeAlgorithm();
    }
}

4.4 步骤四:客户端切换策略


在客户端代码中,通过设置具体的策略对象来切换不同的算法:

java 复制代码
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        
        // 设置为策略A,执行策略A算法
        context.setStrategy(new ConcreteStrategyA());
        context.executeStrategy();
        
        // 设置为策略B,执行策略B算法
        context.setStrategy(new ConcreteStrategyB());
        context.executeStrategy();
    }
}

4.5 动态设置策略


策略模式的强大之处在于可以动态地设置策略 ,而不仅仅是在初始化时设置。通过上下文类的 setStrategy 方法,客户端可以根据需求在运行时动态切换策略,以得到不同的行为。

五、应用场景


策略模式在许多实际场景中都有应用,它特别适合处理那些存在多种行为或算法且需要在运行时动态切换的情况。以下是一些常见的应用场景:

5.1 排序算法


在排序程序中,可能需要支持多种不同的排序算法,如快速排序、归并排序、插入排序等。通过策略模式,可以将每一种排序算法封装成独立的策略类,然后在运行时根据需要选择具体的排序算法。

java 复制代码
// 排序策略接口
interface SortStrategy {
    void sort(int[] array);
}

// 快速排序策略
class QuickSortStrategy implements SortStrategy {
    public void sort(int[] array) {
        // 快速排序算法实现
    }
}

// 归并排序策略
class MergeSortStrategy implements SortStrategy {
    public void sort(int[] array) {
        // 归并排序算法实现
    }
}

// 排序上下文类
class SortContext {
    private SortStrategy strategy;

    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void sortArray(int[] array) {
        strategy.sort(array);
    }
}

// 客户端
public class SortClient {
    public static void main(String[] args) {
        SortContext context = new SortContext();
        int[] array = {5, 3, 8, 6, 2};

        // 使用快速排序
        context.setStrategy(new QuickSortStrategy());
        context.sortArray(array);
        
        // 使用归并排序
        context.setStrategy(new MergeSortStrategy());
        context.sortArray(array);
    }
}

5.2 支付方式


在电子商务系统中,用户可能需要选择不同的支付方式,如信用卡支付、支付宝支付、微信支付等。通过策略模式,可以将每种支付方式封装成独立的策略类,用户可以在运行时选择具体的支付方式。

java 复制代码
// 支付策略接口
interface PaymentStrategy {
    void pay(double amount);
}

// 信用卡支付策略
class CreditCardPaymentStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("使用信用卡支付" + amount + "元");
    }
}

// 支付宝支付策略
class AlipayPaymentStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("使用支付宝支付" + amount + "元");
    }
}

// 支付上下文类
class PaymentContext {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void makePayment(double amount) {
        strategy.pay(amount);
    }
}

// 客户端
public class PaymentClient {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();
        
        // 使用信用卡支付
        context.setStrategy(new CreditCardPaymentStrategy());
        context.makePayment(100.0);
        
        // 使用支付宝支付
        context.setStrategy(new AlipayPaymentStrategy());
        context.makePayment(200.0);
    }
}

5.3 数据压缩


在数据压缩程序中,可能需要使用不同的压缩算法,如ZIP、RAR、TAR等。通过策略模式,可以将每种压缩算法封装成独立的策略类,然后在运行时选择具体的压缩算法。

java 复制代码
// 压缩策略接口
interface CompressionStrategy {
    void compress(String data);
}

// ZIP压缩策略
class ZipCompressionStrategy implements CompressionStrategy {
    public void compress(String data) {
        System.out.println("使用ZIP压缩数据:" + data);
    }
}

// RAR压缩策略
class RarCompressionStrategy implements CompressionStrategy {
    public void compress(String data) {
        System.out.println("使用RAR压缩数据:" + data);
    }
}

// 压缩上下文类
class CompressionContext {
    private CompressionStrategy strategy;

    public void setStrategy(CompressionStrategy strategy) {
        this.strategy = strategy;
    }

    public void compressData(String data) {
        strategy.compress(data);
    }
}

// 客户端
public class CompressionClient {
    public static void main(String[] args) {
        CompressionContext context = new CompressionContext();
        
        // 使用ZIP压缩
        context.setStrategy(new ZipCompressionStrategy());
        context.compressData("示例数据");
        
        // 使用RAR压缩
        context.setStrategy(new RarCompressionStrategy());
        context.compressData("示例数据");
    }
}

5.4 游戏AI行为


在游戏开发中,非玩家角色(NPC)的行为策略可以根据不同的情境进行切换,例如攻击行为、防守行为、巡逻行为等。通过策略模式,可以将每种行为封装成独立的策略类,游戏引擎可以根据游戏情境动态切换不同的行为。

java 复制代码
// 行为策略接口
interface BehaviorStrategy {
    void executeBehavior();
}

// 攻击行为策略
class AttackBehaviorStrategy implements BehaviorStrategy {
    public void executeBehavior() {
        System.out.println("执行攻击行为");
    }
}

// 防守行为策略
class DefenseBehaviorStrategy implements BehaviorStrategy {
    public void executeBehavior() {
        System.out.println("执行防守行为");
    }
}

// 巡逻行为策略
class PatrolBehaviorStrategy implements BehaviorStrategy {
    public void executeBehavior() {
        System.out.println("执行巡逻行为");
    }
}

// AI行为上下文类
class AIContext {
    private BehaviorStrategy strategy;

    public void setStrategy(BehaviorStrategy strategy) {
        this.strategy = strategy;
    }

    public void executeCurrentBehavior() {
        strategy.executeBehavior();
    }
}

// 客户端
public class GameClient {
    public static void main(String[] args) {
        AIContext context = new AIContext();
        
        // 执行攻击行为
        context.setStrategy(new AttackBehaviorStrategy());
        context.executeCurrentBehavior();
        
        // 执行防守行为
        context.setStrategy(new DefenseBehaviorStrategy());
        context.executeCurrentBehavior();
        
        // 执行巡逻行为
        context.setStrategy(new PatrolBehaviorStrategy());
        context.executeCurrentBehavior();
    }
}

六、策略模式的变体与扩展

6.1 内联策略


在某些场景中,可能不需要定义独立的策略类,可以通过内联的方式在上下文类中直接定义策略。内联策略适用于简单的场景,但不建议使用在复杂的应用中。

java 复制代码
class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.executeAlgorithm();
    }
}

// 客户端
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        
        // 设置为策略A,执行策略A算法
        context.setStrategy(() -> System.out.println("执行策略A算法"));
        context.executeStrategy();
        
        // 设置为策略B,执行策略B算法
        context.setStrategy(() -> System.out.println("执行策略B算法"));
        context.executeStrategy();
    }
}

6.2 参数化策略


策略模式还可以通过参数化来增强灵活性,使得具体策略类可以接受多个参数来配置其行为。

java 复制代码
interface ParameterizedStrategy {
    void executeAlgorithm(String parameter);
}

class ConcreteParameterizedStrategyA implements ParameterizedStrategy {
    public void executeAlgorithm(String parameter) {
        System.out.println("执行策略A算法,参数:" + parameter);
    }
}

class ConcreteParameterizedStrategyB implements ParameterizedStrategy {
    public void executeAlgorithm(String parameter) {
        System.out.println("执行策略B算法,参数:" + parameter);
    }
}

class ParameterizedContext {
    private ParameterizedStrategy strategy;

    public void setStrategy(ParameterizedStrategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy(String parameter) {
        strategy.executeAlgorithm(parameter);
    }
}

// 客户端
public class ParameterizedStrategyDemo {
    public static void main(String[] args) {
        ParameterizedContext context = new ParameterizedContext();
        
        // 使用参数化策略A
        context.setStrategy(new ConcreteParameterizedStrategyA());
        context.executeStrategy("参数1");
        
        // 使用参数化策略B
        context.setStrategy(new ConcreteParameterizedStrategyB());
        context.executeStrategy("参数2");
    }
}

6.3 缓存策略

在某些应用中,选择策略的过程可能比较耗时,通过缓存策略 对象可以提高性能。可以使用工厂模式或单例模式来创建并缓存策略对象。

java 复制代码
class StrategyFactory {
    private static final Map<String, Strategy> strategies = new HashMap<>();
    
    static {
        strategies.put("A", new ConcreteStrategyA());
        strategies.put("B", new ConcreteStrategyB());
    }

    public static Strategy getStrategy(String type) {
        return strategies.get(type);
    }
}

// 客户端
public class CachedStrategyDemo {
    public static void main(String[] args) {
        Context context = new Context();
        
        // 从工厂获取并使用策略A
        context.setStrategy(StrategyFactory.getStrategy("A"));
        context.executeStrategy();
        
        // 从工厂获取并使用策略B
        context.setStrategy(StrategyFactory.getStrategy("B"));
        context.executeStrategy();
    }
}

七、总结🚀🚀🚀


策略模式作为一种行为型设计模式,通过将算法封装成独立的策略类,实现算法的易于扩展和高可维护性,避免了大量的条件分支语句,使得代码更加清晰、简洁。策略模式在排序算法、支付方式、数据压缩、游戏AI行为等场景中广泛应用。

然而,策略模式引入了额外的类和接口,可能略微增加系统的复杂性,且客户端需要了解所有的策略类,增加了客户端的负担。开发人员在使用策略模式时需要权衡利弊,根据具体情况选择合适的设计方案。

希望本篇关于策略模式的详细解析,能帮助开发人员更好地理解和应用这一设计模式,提高代码质量和系统的扩展性。如果有任何问题或建议,欢迎在评论区留言讨论。

相关推荐
q56731523几秒前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平25 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
duration~40 分钟前
Maven随笔
java·maven
zmgst44 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
研究是为了理解1 小时前
Git Bash 常用命令
git·elasticsearch·bash
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb