一、引言
在软件开发过程中,我们常常会遇到这样的情况:针对同一个问题,有多种不同的解决方案。比如在排序算法中,有冒泡排序、快速排序、归并排序等。如果我们把这些不同的算法都写在一个类中,通过条件判断来选择使用哪种算法,这样的代码不仅冗长,而且可维护性和扩展性都很差。策略模式正是为了解决这类问题而诞生的。
二、策略模式的定义与概念
策略模式定义了一系列算法,将每个算法都封装起来,并且使它们之间可以互换。策略模式使算法的变化独立于使用算法的客户。
(一)角色分析
- 抽象策略角色(Strategy):定义了一个公共接口,各种具体的策略类实现这个接口。该接口规定了具体策略类必须实现的方法。
- 具体策略角色(ConcreteStrategy):实现了抽象策略角色所定义的接口,提供具体的算法实现。
- 环境角色(Context):持有一个抽象策略类的引用,提供一个方法来设置具体策略类,以便在运行时根据需要切换策略。
三、Java 代码实现策略模式
(一)创建抽象策略角色
首先,我们定义一个抽象的策略接口,以计算折扣为例。
public interface DiscountStrategy {
double calculateDiscount(double price);
}
(二)创建具体策略角色
无折扣策略
public class NoDiscountStrategy implements DiscountStrategy {
@Override
public double calculateDiscount(double price) {
return price;
}
}
固定折扣策略(例如 8 折)
public class FixedDiscountStrategy implements DiscountStrategy {
@Override
public double calculateDiscount(double price) {
return price * 0.8;
}
}
满减折扣策略(例如满 100 减 20)
public class FullReductionDiscountStrategy implements DiscountStrategy {
@Override
public double calculateDiscount(double price) {
if (price >= 100) {
return price - 20;
}
return price;
}
}
(三)创建环境角色
public class ShoppingCart {
private DiscountStrategy discountStrategy;
public ShoppingCart(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateTotalPrice(double originalPrice) {
return discountStrategy.calculateDiscount(originalPrice);
}
}
(四)测试代码
public class StrategyPatternTest {
public static void main(String[] args) {
// 使用无折扣策略
ShoppingCart cart1 = new ShoppingCart(new NoDiscountStrategy());
double price1 = 150;
double total1 = cart1.calculateTotalPrice(price1);
System.out.println("无折扣时,总价为:" + total1);
// 使用固定折扣策略
ShoppingCart cart2 = new ShoppingCart(new FixedDiscountStrategy());
double price2 = 150;
double total2 = cart2.calculateTotalPrice(price2);
System.out.println("8 折折扣时,总价为:" + total2);
// 使用满减折扣策略
ShoppingCart cart3 = new ShoppingCart(new FullReductionDiscountStrategy());
double price3 = 150;
double total3 = cart3.calculateTotalPrice(price3);
System.out.println("满 100 减 20 折扣时,总价为:" + total3);
// 动态切换策略
cart3.setDiscountStrategy(new FixedDiscountStrategy());
double total4 = cart3.calculateTotalPrice(price3);
System.out.println("切换为 8 折折扣后,总价为:" + total4);
}
}
四、策略模式的关键要点
(一)封装变化
将不同的算法封装在各自的具体策略类中,这样当需要新增或修改算法时,只需要修改或新增具体策略类,而不会影响到其他部分的代码。例如,如果我们要新增一种 “买一送一” 的折扣策略,只需要创建一个新的具体策略类实现 DiscountStrategy 接口,而不需要修改 ShoppingCart 类或其他已有的策略类。
(二)提高可维护性和扩展性
通过将算法独立封装,使得代码结构更加清晰,易于维护。同时,新增策略也非常方便,符合开闭原则。例如,在电商系统中,如果需要增加新的促销活动策略,直接添加新的策略类即可,而不需要在一个庞大的类中添加大量的条件判断代码。
(三)运行时动态切换
环境角色可以在运行时根据不同的条件动态切换具体的策略。比如在上述购物车的例子中,我们可以根据用户的会员等级、活动时间等条件,在运行时动态地为购物车设置不同的折扣策略,实现灵活的业务逻辑。
(四)避免多重条件判断
如果不使用策略模式,我们可能会在一个方法中使用大量的 if - else 或 switch - case 语句来选择不同的算法,这样的代码不仅难以阅读和维护,而且当新增算法时,需要修改这个复杂的条件判断语句。而策略模式通过将不同算法封装成独立的类,避免了这种多重条件判断的情况。
五、策略模式的应用场景
算法的选择:在排序算法、搜索算法等场景中,不同的算法适用于不同的数据规模和数据特点。使用策略模式可以根据具体情况选择合适的算法。
业务规则的变化:在电商系统中,促销活动规则经常变化,如不同节日的不同折扣策略。策略模式可以很好地应对这种变化,方便地添加或修改促销规则。
行为的动态变化:游戏开发中,角色的移动方式、攻击方式等行为可以使用策略模式实现动态变化。例如,角色在不同场景下可能有不同的移动速度和攻击方式。
六、策略模式的优缺点
(一)优点
灵活性高:可以根据不同的需求动态切换算法,提高了系统的灵活性。
可维护性好:每个策略类都是独立的,修改一个策略类不会影响其他策略类和系统的其他部分。
符合开闭原则:易于扩展新的策略,满足不断变化的业务需求。
(二)缺点
客户端需要了解不同的策略:客户端在使用策略模式时,需要知道有哪些具体的策略可供选择,并且要根据实际情况选择合适的策略。这可能会增加客户端的使用难度。
策略类数量增多:如果有大量的策略,会导致策略类的数量增多,增加系统的复杂性。