彻底搞懂策略模式

282 阅读9分钟

策略设计模式是一种在编程中非常有用的模式。它允许将对象的行为进行封装,使其能够在不同的策略之间动态切换,从而改变对象的行为方式。这就好比一个物体可以根据不同的情况选择不同的运行模式,以适应各种需求。

策略设计模式的优点

  1. 提升代码灵活性:类似于搭建积木,每个策略如同一块积木,能够方便地替换或修改,而不会对整体结构造成影响。如此一来,代码便能更轻松地依据需求变化进行调整。

  2. 增强代码复用性:这些策略可以在不同的对象和项目中重复使用,就像一把万能钥匙可以开启多把锁,大大提高了代码的利用效率。

  3. 促进良好编码习惯:该模式促使开发者将不同的关注点分离,如同将不同种类的物品分别放置在不同的盒子中,使代码结构更加清晰,降低了复杂性。

  4. 简化测试过程:由于行为和对象分离开来,测试时可以单独对每个策略进行检查,如同逐个检查物品是否完好无损,使测试更加直接和简便。

使用场景

  1. 排序算法方面:例如在对一组数据进行排序时,不同的排序算法(如冒泡排序、快速排序等)可以被看作是不同的策略。可以根据数据的特点和需求,选择合适的排序策略来完成排序任务。

  2. 验证规则方面:当需要检查用户输入的数据是否符合特定要求时,不同的验证规则(如检查邮箱格式是否正确、密码强度是否达标等)可视为不同的策略,根据实际情况选用相应的规则进行验证。

  3. 文本格式化方面:若要将一段文字按照不同的格式要求进行排版(如设置为标题格式、正文格式、列表格式等),每种格式就相当于一个策略,方便根据具体需求调整文本的呈现样式。

  4. 数据库访问方面:从不同类型的数据库(如 MySQL、Oracle 等)获取数据时,针对每个数据库的访问方式都可以作为一种策略。这样就能够方便地在不同数据源之间进行切换。

  5. 支付策略方面:在进行购物支付时,信用卡支付、支付宝支付、微信支付等不同的支付方式都可作为不同的策略。根据用户的选择,调用相应的支付策略来处理支付流程。

策略设计模式的组件

  1. 上下文(Context):可以将其想象成一个指挥者,它负责决定使用何种策略。上下文持有策略对象的引用,并通过公共接口与策略进行交互,就像指挥者依据特定的指令指挥执行者行动一样。

  2. 策略接口(Strategy Interface):这类似于一种行为规范或模板,规定了所有策略都应具备的行为方法。就好比所有执行者都需要遵循的一套基本操作准则。

  3. 具体策略(Concrete Strategies):这些是真正实现具体行为的执行者。每个具体策略都封装了独特的行为逻辑,上下文可以在运行时根据需要切换到不同的具体策略,就像指挥者可以选择不同技能的执行者来完成任务。

工作原理

把对象的行为类比为执行者的技能,将这些技能从执行者本身分离出来,形成独立的“技能模块”(策略)。上下文如同执行者的装备库,其中存放着这些技能模块的使用说明(引用策略对象)。当面临任务时,上下文从装备库中选取合适的技能模块,并按照说明调用相应的技能(通过公共接口调用策略方法)。若遇到不同的任务需求,还可以随时更换为其他技能模块,从而改变执行者的行为方式。

实际示例

  1. 在音乐流媒体服务中,不同的订阅套餐类似于不同的服务模式,每个套餐都有其独特的定价策略。例如,高级套餐可能提供更多特权,其定价策略与普通套餐有所不同。计费系统就像指挥者,根据用户选择的套餐,运用相应的定价策略来计算费用。这样一来,若要调整某个套餐的价格,只需修改对应的策略即可,操作十分便捷。

  2. 在购物车应用程序中,支付方式如同购物过程中的不同操作选项。信用卡支付、PayPal 支付、加密货币支付等分别代表不同的策略。当用户选择支付时,应用程序就像指挥者根据用户的选择,调用相应的支付策略来处理支付流程。倘若日后出现新的支付方式,只需添加一个新的支付策略类即可,不会影响到现有支付方式的处理逻辑。

如何实现策略设计模式

  1. 首先确定需要封装并能够在运行时灵活切换的算法或行为,例如前面提到的排序算法、支付方式等。

  2. 接着定义一个类似行为规范的接口,该接口包含一个方法,这个方法应能接收必要的参数,比如支付场景中的支付金额、排序场景中的待排序数据等。

  3. 然后为每个具体的行为实现该接口,即每个具体策略类都要实现接口中的方法,以提供其独特的行为逻辑,就像每个执行者都要依据基本操作准则展现自己独特的执行方式。

  4. 之后定义一个上下文类,这个类类似于执行者的装备库,它持有策略接口的引用,并能在需要时按照接口规范调用策略的方法。

  5. 最后修改上下文类,使其能够在程序运行时方便地切换不同的具体策略实现,就像指挥者可以在不同情况下选择不同技能的执行者。

代码示例

先看一段存在问题的代码:

package withoutstrategy;

public class PaymentProcessor {
    private PaymentType paymentType;

    public void processPayment(double amount) {
        if (paymentType == PaymentType.CREDIT_CARD) {
            System.out.println("Processing credit card payment of amount " + amount);
        } else if (paymentType == PaymentType.DEBIT_CARD) {
            System.out.println("Processing debit card payment of amount " + amount);
        } else if (paymentType == PaymentType.PAYPAL) {
            System.out.println("Processing PayPal payment of amount " + amount);
        } else {
            throw new IllegalArgumentException("Invalid payment type");
        }
    }

    public void setPaymentType(PaymentType paymentType) {
        this.paymentType = paymentType;
    }

    enum PaymentType {
        CREDIT_CARD,
        DEBIT_CARD,
        PAYPAL
    }
}

在这段代码中,PaymentProcessor类类似于一个不够智能的指挥者,它通过判断paymentType来处理支付。若要添加一种新的支付方式,就必须修改processPayment方法,这如同要给指挥者重新培训一项新技能,既麻烦又容易出错,而且代码中大量的条件判断使逻辑不够清晰,灵活性欠佳。

下面使用策略设计模式进行改进: 首先定义PaymentStrategy接口,这是所有支付策略的行为规范:

package withstrategy;

public interface PaymentStrategy {
    void processPayment(double amount);
}

然后为每种支付类型实现该接口,例如信用卡支付策略:

package withstrategy;

public class CreditCardPaymentStrategy implements PaymentStrategy {
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of amount " + amount);
    }
}

借记卡支付策略:

package withstrategy;

public class DebitCardPaymentStrategy implements PaymentStrategy {
    public void processPayment(double amount) {
        System.out.println("Processing debit card payment of amount " + amount);
    }
}

PayPal 支付策略:

package withstrategy;

public class PaypalPaymentStrategy implements PaymentStrategy {
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of amount " + amount);
    }
}

最后更新PaymentProcessor类,使其成为一个智能的指挥者:

package withstrategy;

public class PaymentProcessor {
    private PaymentStrategy paymentStrategy;

    public PaymentProcessor(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void processPayment(double amount) {
        paymentStrategy.processPayment(amount);
    }
}

这样,若以后要添加新的支付方式,只需创建一个新的类实现PaymentStrategy接口即可,无需修改原有代码,极大地提高了代码的可扩展性和灵活性。

最佳实践

  1. 接口设计应简洁明了,专注于单一职责,如同执行者的技能应简单易懂且专注于特定任务,避免接口过于复杂导致代码混乱。

  2. 若策略具有自身的状态信息(如记录支付次数等),应将这些状态封装在具体策略类中,而非上下文类,就像执行者的个人状态应由其自身管理,不应让指挥者过多干预。

  3. 在将具体策略传递给上下文类时,建议采用依赖注入的方式,这类似于给指挥者配备技能模块,而不是在上下文类内部直接创建策略,这样可以提高代码的灵活性和可测试性。

  4. 可以利用枚举或工厂类来统一管理具体策略对象的创建过程,就像建立一个专门的执行者培训中心,负责训练和管理各种执行者,使代码结构更加清晰有序。

实际应用

Java 的集合框架广泛运用了策略设计模式。例如sort()方法,它就像一个智能的排序指挥者。当对一个集合进行排序时,可以提供一个Comparator对象作为排序策略。不同的Comparator实现可以定义不同的比较规则,如按照数字大小、字母顺序等。sort()方法依据提供的策略对集合进行排序。此外,Iterator接口定义了访问集合元素的策略,通过使用不同的Iterator,可以以不同的方式遍历集合,就像使用不同的工具来探索一个区域。

总结

在本教程中,我们深入探索了策略设计模式。该模式通过将对象的行为与对象本身分离,如同将执行者与技能分离管理,使代码变得更加灵活且易于维护。我们详细了解了其组件,包括上下文、策略接口和具体策略,就像认识了一个团队中的不同角色及其职责。通过支付系统等实际示例,我们体会到了该模式在应对各种变化需求时的强大之处。希望你在阅读本教程后,能够对策略设计模式有更深入的理解,并在编程实践中熟练运用。