设计模式学习之——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它允许定义一系列算法,并将每个算法封装在独立的类中,使它们可以互相替换。策略模式通过将算法的使用与算法的实现分离,使得算法可以独立于客户端而变化。
一、策略模式的核心思想
策略模式的核心思想是面向接口编程,而不是面向实现编程。它通过将算法封装成独立的类,使得可以在运行时动态地选择和切换算法,从而提高代码的可维护性、扩展性和复用性。
二、策略模式的主要角色
在策略模式结构图中,通常包含以下几个角色:
- 策略接口(Strategy):定义了一个或多个策略算法的方法,这些方法是所有具体策略类必须实现的。策略接口使得算法可以独立于使用它的客户而变化。
- 具体策略类(Concrete Strategies):实现了策略接口,提供了具体的算法实现。每个具体策略类都对应一种具体的算法。
- 上下文类(Context):持有一个策略接口的引用,在客户端调用上下文类的某个方法时,上下文类会把请求委托给当前持有的策略对象。上下文类通常还包含一个设置策略对象的方法,以便在运行时动态地切换策略。
三、策略模式的使用场景
策略模式通常适用于以下场景:
- 多算法选择:当需要在运行时根据情况选择不同的算法时,可以使用策略模式。例如,对于排序算法,根据数据量的不同可能选择快速排序、冒泡排序或插入排序等。
- 消除条件分支:当代码中存在大量的条件分支语句,并且这些条件分支都是根据相同的输入来选择不同的行为时,可以考虑使用策略模式来消除这些条件分支。这可以提高代码的可读性和可维护性。
- 算法的封装和复用:当系统中存在多个类似的算法,但它们的实现细节不同时,可以将这些算法封装成独立的策略类,以便复用和维护。
- 可扩展性:当需要为系统提供一种灵活、可拓展的方式来添加新的算法或行为时,策略模式可以帮助实现这一点,而无需修改现有的代码。
- 单一职责原则:当需要遵循单一职责原则,即每个类应该只负责一种功能时,策略模式可以将不同的算法分离到单独的策略类中,使得每个类都专注于一种算法。
四、策略模式的优缺点
优点:
- 提高了代码的灵活性和可维护性:通过策略模式,可以在运行时动态地选择和切换算法,而不需要修改客户端代码。这使得代码更加灵活和易于维护。
- 简化了单元测试:由于策略模式将算法封装在独立的类中,因此可以针对每个算法进行单独的单元测试,从而简化了测试过程。
- 遵循了开闭原则:策略模式可以在不修改现有代码的情况下添加新的算法或行为,这符合开闭原则的要求。
缺点:
- 策略类数量多:当算法的数量较多时,会导致策略类的数量增加,从而增加了系统的复杂性。
- 客户端需要了解策略:客户端代码需要知道有哪些策略可供选择,并创建相应的策略对象,这可能会增加客户端的复杂度。
五、策略模式的示例
以下是几个简单的策略模式示例。
示例一:促销活动
- 满减促销:当顾客购买一定金额的商品后,可以直接减去相应的金额。
- 返现促销:在顾客购买商品后,返回一定比例的现金或优惠券。
- 打折促销:对选定商品或全部商品进行打折处理。
- 买赠促销:购买特定商品时赠送其他商品或服务
// 促销策略接口
public interface PromotionStrategy {
double calculatePrice(double originalPrice);
}
// 满减策略
public class FullReductionStrategy implements PromotionStrategy {
private double threshold;
private double reduction;
public FullReductionStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double calculatePrice(double originalPrice) {
if (originalPrice >= threshold) {
return originalPrice - reduction;
}
return originalPrice;
}
}
// 打折策略
public class DiscountStrategy implements PromotionStrategy {
private double discountRate;
public DiscountStrategy(double discountRate) {
this.discountRate = discountRate;
}
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * discountRate;
}
}
// 上下文类
public class ShoppingCart {
private PromotionStrategy promotionStrategy;
public void setPromotionStrategy(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public double getTotalPrice(double originalPrice) {
return promotionStrategy.calculatePrice(originalPrice);
}
}
// 客户端代码
public class PromotionDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// 使用满减策略
cart.setPromotionStrategy(new FullReductionStrategy(100, 20));
System.out.println("满减后价格: " + cart.getTotalPrice(120)); // 输出: 100.0
// 使用打折策略
cart.setPromotionStrategy(new DiscountStrategy(0.8));
System.out.println("打折后价格: " + cart.getTotalPrice(120)); // 输出: 96.0
}
}
示例二:排序与搜索
- 数组排序:根据不同需求,可以选择快速排序、归并排序、堆排序等算法进行数组排序。
- 数据搜索:在大量数据中搜索目标值时,可以采用二分搜索、线性搜索等不同算法。
// 排序策略接口
public interface SortStrategy {
void sort(int[] array);
}
// 快速排序策略
public class QuickSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
// 快速排序的具体实现
// ...
// 这里为了简化,省略了具体实现
System.out.println("执行快速排序");
}
}
// 冒泡排序策略
public class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
// 冒泡排序的具体实现
// ...
// 这里为了简化,省略了具体实现
System.out.println("执行冒泡排序");
}
}
// 上下文类
public class Sorter {
private SortStrategy sortStrategy;
public void setSortStrategy(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
public void sortArray(int[] array) {
sortStrategy.sort(array);
}
}
// 客户端代码
public class SortDemo {
public static void main(String[] args) {
int[] array = {5, 3, 8, 4, 2};
Sorter sorter = new Sorter();
// 使用快速排序
sorter.setSortStrategy(new QuickSortStrategy());
sorter.sortArray(array);
// 使用冒泡排序
sorter.setSortStrategy(new BubbleSortStrategy());
sorter.sortArray(array);
}
}
示例三:支付方式
- 信用卡支付:使用信用卡结算,支持各种主流信用卡。
- 第三方支付:如支付宝、微信支付等,满足不同用户习惯。
- 货到付款:在收货时付款,提高用户信任度。
// 策略接口
public interface PaymentStrategy {
void pay(int amount);
}
// 具体策略类:信用卡支付
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
public void pay(int amount) {
System.out.println(amount + " paid with credit card.");
}
}
// 具体策略类:PayPal支付
public class PayPalPayment implements PaymentStrategy {
private String emailId;
public PayPalPayment(String email) {
this.emailId = email;
}
public void pay(int amount) {
System.out.println(amount + " paid using PayPal.");
}
}
// 上下文类
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// 客户端代码
public class StrategyPatternExample {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234567890123456"));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("myemail@example.com"));
cart.checkout(200);
}
}
示例阶段小总结:(后面的示例将不再编写具体代码,参考方法套用实现即可)
定义策略接口:
首先,你需要定义一个策略接口,这个接口将声明所有支持的策略算法所共有的方法。这个接口就是算法的家族,为一系列具体的策略类提供了统一的接口
// 策略接口
public interface Strategy {
void execute(); // 声明算法的方法
}
实现具体策略类:
然后,你需要为每一个具体的算法实现一个具体的策略类,这些类都实现了策略接口。每个具体策略类都提供了一个具体的算法实现。
// 具体策略类A
public class ConcreteStrategyA implements Strategy {
public void execute() {
// 算法A的具体实现
System.out.println("执行算法A");
}
}
// 具体策略类B
public class ConcreteStrategyB implements Strategy {
public void execute() {
// 算法B的具体实现
System.out.println("执行算法B");
}
}
创建上下文类:
上下文类持有一个策略接口的引用,它维护一个对具体策略的引用。客户端代码通过调用上下文类的方法来间接调用具体策略的方法。上下文类还可以提供一个方法来设置当前使用的策略。
// 上下文类
public class Context {
private Strategy strategy;
// 设置当前策略
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
// 执行当前策略的方法
public void executeStrategy() {
strategy.execute();
}
}
客户端代码:
最后,在客户端代码中,你可以根据需要动态地设置和切换策略。客户端代码通过创建上下文对象,并设置具体的策略来实现这一点。
// 客户端代码
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();
}
}
关键点总结
- 策略接口:定义了所有策略算法所共有的方法。
- 具体策略类:实现了策略接口,提供了具体的算法实现。
- 上下文类:持有一个策略接口的引用,通过调用该接口的方法来间接调用具体策略的方法。
- 客户端代码:通过创建上下文对象,并设置具体的策略来实现算法的动态选择和切换。
示例四:数据压缩
- 无损压缩:如zip、gzip等,保证数据完整性。
- 有损压缩:如jpeg、mp3等,适用于图片和音频数据。
示例五:游戏开发
- 敌人行为:根据不同关卡和难度,切换敌人的行为策略。
- 玩家辅助:根据玩家等级提供不同的辅助策略,如自动瞄准、提示等。
示例六:出行选择
- 出行方式的选择,如乘坐飞机、火车、自行车等,可以根据天气、距离、时间紧迫等因素决定采用哪一种方式出行。
示例七:表单验证
- 一个表单验证工具可以根据不同的验证规则采用不同的验证策略,例如长度验证、格式验证等。
示例八:个人所得税计算
- 不同国家或地区的个人所得税计算方法可能不同,策略模式可以使得这些不同的计算方法可以相互替换。
示例九:动物叫声模拟
- 可以模拟不同动物的叫声,如狗的汪汪叫、猫的喵喵叫、鸟的啾啾叫等。
通过策略模式,可以将算法或行为与具体的业务逻辑解耦,使得系统更加灵活和可扩展。它允许在运行时动态地选择和切换算法,而无需修改原有的代码,从而提高了代码的复用性和可维护性。同时,策略模式也符合开闭原则,即对扩展开放,对修改关闭,使得系统可以更容易地应对未来可能的需求变化。