设计模式|策略模式
**策略模式(Strategy Pattern)**是一种软件设计模式,属于行为型模式。它允许在运行时根据不同情况选择算法的行为。
✍🏻定义
在策略模式中,将各种算法封装成不同的策略(Strategy),并使它们可以互相替换。每个策略都完成一个特定的任务,但是它们的接口是相同的,这样业务就可以在不知道具体实现的情况下使用不同的策略。
🍭优点
- 提供了更好的代码重用性,可以通过增加新的策略类来扩展系统功能,而不需要修改原有代码。
- 提供了更好的扩展性,可以方便地增加新的策略,满足不同的需求。
- 可以避免使用大量的条件语句,提高了代码的可读性和可维护性。
组成成员
策略接口(Strategy Interface)
策略接口定义了策略模式的公共接口。它通常包含一个或多个方法,用于执行特定的算法或操作。所有的具体策略类都必须实现这个接口,以确保它们具有相同的方法签名。
具体策略类(Concrete Strategies)
具体策略类实现了策略接口,并封装了具体的算法或操作。每个具体策略类提供了不同的实现,用于完成特定的任务。客户端可以根据需求选择不同的具体策略类。
上下文类(Context)
上下文类是策略模式的核心组件,它持有一个策略对象的引用,并在需要执行特定算法或操作时调用策略对象的方法。上下文类通常包含一个设置策略的方法,用于在运行时动态地切换策略。
客户端(Client)
客户端是使用策略模式的调用者。它通过实例化上下文类并设置具体策略对象来选择要使用的算法或操作。客户端根据自身的需求,在不同的情况下选择不同的策略,从而实现灵活的算法切换。
📖经典例子
我们使用java来写一个贴切实际业务的策略模式的例子
支付策略接口
1 2 3
| public interface PaymentStrategy { void pay(double amount); }
|
具体策略类
信用卡支付
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class CreditCardPaymentStrategy implements PaymentStrategy { private String cardNumber; private String expirationDate; private String cvv;
public CreditCardPaymentStrategy(String cardNumber, String expirationDate, String cvv) { this.cardNumber = cardNumber; this.expirationDate = expirationDate; this.cvv = cvv; }
@Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过信用卡号: " + cardNumber); } }
|
支付宝支付
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class AlipayPaymentStrategy implements PaymentStrategy { private String userId; private String password;
public AlipayPaymentStrategy(String userId, String password) { this.userId = userId; this.password = password; }
@Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过支付宝账户: " + userId); } }
|
上下文类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Order { private double amount; private PaymentStrategy paymentStrategy;
public Order(double amount, PaymentStrategy paymentStrategy) { this.amount = amount; this.paymentStrategy = paymentStrategy; } public void makePayment() { paymentStrategy.pay(amount); } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main { public static void main(String[] args) { Order order1 = new Order(100.0, new CreditCardPaymentStrategy("1234 5678 9012 3456", "12/25", "123")); Order order2 = new Order(200.0, new AlipayPaymentStrategy("user123", "password456"));
order1.makePayment(); System.out.println(); order2.makePayment(); } }
|
输出结果
1 2 3 4
| 支付 ¥100.0 通过信用卡号: 1234 5678 9012 3456
支付 ¥200.0 通过支付宝账户: user12
|
小结
结合上述例子,可以得出结论:
策略接口定义了策略模式的公共接口,具体策略类实现了该接口并封装了具体的策略算法。上下文类持有策略对象并在需要时调用其方法,而客户端则负责创建具体策略对象并将其传递给上下文类。这样,客户端可以根据不同的情况选择不同的策略,并将其应用于上下文类的操作中。
当业务需要更多执行策略的时候,只需创建具体策略类(继承策略公共接口)即可,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class WeChatPaymentStrategy implements PaymentStrategy { private String appId; private String appSecret;
public WeChatPaymentStrategy(String appId, String appSecret) { this.appId = appId; this.appSecret = appSecret; }
@Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过微信APP ID: " + appId); } }
|
且在具体执行逻辑中
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main { public static void main(String[] args) { Order order1 = new Order(100.0, new CreditCardPaymentStrategy("1234 5678 9012 3456", "12/25", "123")); - Order order2 = new Order(200.0, new AlipayPaymentStrategy("user123", "password456")); + Order order2 = new Order(150.0, new WeChatPaymentStrategy("12345678", "a1b2c3d4")); order1.makePayment(); System.out.println(); order2.makePayment(); } }
|
结果
1 2 3 4
| 支付 ¥100.0 通过信用卡号: 1234 5678 9012 3456
支付 ¥150.0 通过微信AOO ID: 12345678
|
在本例中,业务的替换或拓展通过策略模式的优化将逻辑业务转到了策略类中,降低了工程的耦合度和可维护性。
🍺枚举策略
🙋🏻♂️问:
策略模式的每次扩展都需要添加策略类,随着业务的不断扩展,显然这是一个弊端。能否解决这个问题呢?
💡当然可以!
我们可以使用枚举策略
集合策略接口、具体策略类、上下文类
我们可以通过枚举的特性来优化
枚举类中的抽象方法的实现,需要枚举类中的每个元素都对其进行实现。
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
| public enum PaymentStrategy { CREDIT_CARD { @Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过信用卡"); } }, ALIPAY { @Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过支付宝"); } }, WECHAT { @Override public void pay(double amount) { System.out.println("支付 ¥" + amount + " 通过微信"); } };
public abstract void pay(double amount); }
|
此时枚举类中的每个元素代表的即是一个策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Main { public static void main(String[] args) { double amount = 100.0;
PaymentStrategy.CREDIT_CARD.pay(amount);
PaymentStrategy.ALIPAY.pay(amount);
PaymentStrategy.WECHAT.pay(amount); } }
|
1 2 3 4
| 支付 ¥100.0 通过信用卡 支付 ¥100.0 通过支付宝 支付 ¥100.0 通过微信
|
🙋🏻♂️问:
有没有办法可以通过业务传递的动态值来选择策略(而不是写死在程序上)?
💡当然可以!
我们可以通过枚举的valueOf
方法
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main { public static void main(String[] args) { double amount = 100.0;
String paymentMethod = "WECHAT"; PaymentStrategy strategy = PaymentStrategy.valueOf(paymentMethod);
strategy.pay(amount); } }
|
✨使用函数式接口优化策略模式
在Java8中引入了函数式编程概念以及Lambda表达式
定义函数式接口
1 2 3 4
| @FunctionalInterface interface PaymentStrategy { void pay(double amount); }
|
具体业务实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Main { public static void main(String[] args) { double amount = 100.0;
PaymentStrategy creditCardStrategy = (amt) -> { };
PaymentStrategy alipayStrategy = (amt) -> { };
PaymentStrategy wechatStrategy = (amt) -> { };
creditCardStrategy.pay(amount); alipayStrategy.pay(amount); wechatStrategy.pay(amount); } }
|
💥注意
如果业务只有一个或很少数的分支行为,又或者是无需持续维护或拓展分支功能的业务,那或许该考虑是否适合用策略模式。
🙅🏻♂️请不要为了使用设计模式而尬写设计模式!
🙅🏿♂️请不要为了使用设计模式而尬写设计模式!
🙅♂️请不要为了使用设计模式而尬写设计模式!
🙎🏻: 我测
🎍扩展
当然同一个业务下,不同策略类也会有重复的流程,可以结合模板方法设计模式
来优化业务的耦合部分,提高可维护性。
总结
策略模式是一种行为设计模式,它允许在运行时根据不同的情况选择算法或行为。该模式将算法封装在独立的策略类中,并通过一个上下文类将客户端与具体的策略解耦。客户端可以动态地选择所需的策略,而无需了解其具体实现。
策略模式可以优化代码中的分支语句。传统的条件语句(如if-else或switch)在算法逻辑较多或复杂时,容易导致代码臃肿、难以维护。而策略模式通过将不同的算法封装成独立的策略类,使得代码更加清晰、可读性更强,并且易于扩展。
合理的使用策略模式可以提高业务代码的可维护性。使得代码更加清晰、可读性更强,并且易于扩展。
🛵ENDING…(开走了)
技术分享 — 2023年12月15日