策略模式(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;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic void sort(int[] array) {// 快速排序的具体实现// ...// 这里为了简化,省略了具体实现System.out.println("执行快速排序");}
}// 冒泡排序策略
public class BubbleSortStrategy implements SortStrategy {@Overridepublic 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();// 使用算法Acontext.setStrategy(new ConcreteStrategyA());context.executeStrategy();// 使用算法Bcontext.setStrategy(new ConcreteStrategyB());context.executeStrategy();}
}
关键点总结
- 策略接口:定义了所有策略算法所共有的方法。
- 具体策略类:实现了策略接口,提供了具体的算法实现。
- 上下文类:持有一个策略接口的引用,通过调用该接口的方法来间接调用具体策略的方法。
- 客户端代码:通过创建上下文对象,并设置具体的策略来实现算法的动态选择和切换。
示例四:数据压缩
- 无损压缩:如zip、gzip等,保证数据完整性。
- 有损压缩:如jpeg、mp3等,适用于图片和音频数据。
示例五:游戏开发
- 敌人行为:根据不同关卡和难度,切换敌人的行为策略。
- 玩家辅助:根据玩家等级提供不同的辅助策略,如自动瞄准、提示等。
示例六:出行选择
- 出行方式的选择,如乘坐飞机、火车、自行车等,可以根据天气、距离、时间紧迫等因素决定采用哪一种方式出行。
示例七:表单验证
- 一个表单验证工具可以根据不同的验证规则采用不同的验证策略,例如长度验证、格式验证等。
示例八:个人所得税计算
- 不同国家或地区的个人所得税计算方法可能不同,策略模式可以使得这些不同的计算方法可以相互替换。
示例九:动物叫声模拟
- 可以模拟不同动物的叫声,如狗的汪汪叫、猫的喵喵叫、鸟的啾啾叫等。
通过策略模式,可以将算法或行为与具体的业务逻辑解耦,使得系统更加灵活和可扩展。它允许在运行时动态地选择和切换算法,而无需修改原有的代码,从而提高了代码的复用性和可维护性。同时,策略模式也符合开闭原则,即对扩展开放,对修改关闭,使得系统可以更容易地应对未来可能的需求变化。