策略模式(Strategy Pattern)核心思想是将算法的实现从使用该算法的类中分离出来,作为独立的对象,通过接口来定义算法家族,这样就可以很容易地改变或扩展算法。通过这种方式,可以避免在客户端代码中使用大量的条件语句来选择不同的行为方式,从而提高了代码的灵活性和可维护性。
一、核心思想
策略模式(Strategy Pattern)是一种行为设计模式,它允许对象在运行时动态地选择算法或行为。其核心思想是“定义一系列算法,将每个算法都封装起来,并且使它们之间可以互换”,从而增加系统的灵活性和可扩展性。
二、定义与结构
- 定义:策略模式是一种行为设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
- 结构:
- 环境(Context)类:它是使用策略的类,维护一个对策略对象的引用。在游戏角色的例子中,角色类就是环境类,它会有一个属性来保存攻击策略。
- 抽象策略(Strategy)类:定义了一个公共接口,用于所有具体策略类实现。比如定义一个
attack()
接口,所有不同的攻击策略都要实现这个接口。 - 具体策略(Concrete Strategy)类:实现了抽象策略类中的接口,提供具体的算法实现。例如战士的近身攻击策略类和法师的远程攻击策略类。
三、角色
- 环境(Context)角色
- 持有一个具体策略的对象,可以动态地改变持有的策略对象,并在运行时调用具体策略的方法。
- 抽象策略(Strategy)角色
- 定义了一个公共接口,所有的具体策略类都必须实现这个接口。
- 具体策略(Concrete Strategy)角色
- 实现了抽象策略接口,封装了具体的算法或行为。
四、实现步骤及代码示例
- 步骤一:定义抽象策略类
// 抽象策略接口
public interface MemberStrategy {
double calcPrice(double booksPrice);
}
- 步骤二:定义具体策略类
// 具体策略类:初级会员折扣
public class PrimaryMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("对于初级会员没有折扣");
return booksPrice;
}
}
// 具体策略类:中级会员折扣
public class IntermediateMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("对于中级会员的折扣为10%");
return booksPrice * 0.9;
}
}
// 具体策略类:高级会员折扣
public class AdvancedMemberStrategy implements MemberStrategy {
@Override
public double calcPrice(double booksPrice) {
System.out.println("对于高级会员的折扣为20%");
return booksPrice * 0.8;
}
}
- 步骤三:定义环境类
// 环境类
public class Price {
private MemberStrategy strategy;
public Price(MemberStrategy strategy) {
this.strategy = strategy;
}
public double quote(double booksPrice) {
return this.strategy.calcPrice(booksPrice);
}
}
- 步骤四:使用策略模式
// 客户端代码
public class Client {
public static void main(String[] args) {
// 选择并创建需要使用的策略对象
MemberStrategy strategy = new AdvancedMemberStrategy();
// 创建环境对象
Price price = new Price(strategy);
// 计算价格
double quote = price.quote(300);
System.out.println("图书的最终价格为: " + quote);
}
}
五、常见技术框架应用
1、排序算法
在Python中,sorted()
函数就使用了策略模式的思想。sorted()
函数可以接受一个key
参数,这个key
参数实际上就是一个策略。
# 定义一个简单的列表
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
# 按照元素大小排序(默认策略)
sorted_list_default = sorted(my_list)
print(sorted_list_default)
# 定义一个按照元素绝对值大小排序的策略
def absolute_value_key(item):
return abs(item)
sorted_list_absolute = sorted(my_list, key = absolute_value_key)
print(sorted_list_absolute)
2、支付方式
以下是一个使用策略模式在前端框架中实现不同支付方式的示例代码:
// 定义支付策略接口
interface PaymentStrategy {
pay(amount: number): void;
}
// 实现具体的支付策略类(如支付宝支付、微信支付等)
class AlipayStrategy implements PaymentStrategy {
pay(amount: number) {
console.log(`使用支付宝支付了:${amount}元`);
}
}
class WeChatPayStrategy implements PaymentStrategy {
pay(amount: number) {
console.log(`使用微信支付了:${amount}元`);
}
}
// 定义上下文类,用于持有支付策略的引用并在需要时调用具体的支付策略
class PaymentContext {
private paymentStrategy: PaymentStrategy;
setPaymentStrategy(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}
executePayment(amount: number) {
this.paymentStrategy.pay(amount);
}
}
// 使用示例
const context = new PaymentContext();
context.setPaymentStrategy(new AlipayStrategy());
context.executePayment(100); // 输出: 使用支付宝支付了:100元
context.setPaymentStrategy(new WeChatPayStrategy());
context.executePayment(200); // 输出: 使用微信支付了:200元
在这个示例中,我们定义了一个支付策略接口PaymentStrategy
,并实现了两个具体的支付策略类AlipayStrategy
和WeChatPayStrategy
。然后,我们定义了一个上下文类PaymentContext
,它持有一个支付策略的引用,并提供了设置支付策略和执行支付的方法。在客户端代码中,我们可以通过设置不同的支付策略来动态地选择支付方式。
六、应用场景
多个类只在行为上有区别,而算法和行为需要在运行时可互换的场景。
需要避免使用多重条件转移语句的场景。
当需要使用不同的变体算法时。算法族被定义成一系列的算法类,而每类实现一种算法的场景。
策略模式广泛应用于各种需要算法替换和行为切换的场景中,如:
- 促销活动:满减促销、返现促销、打折促销等。
- 排序算法:快速排序、归并排序、堆排序等。
- 数据搜索:二分搜索、线性搜索等。
- 支付方式:信用卡支付、第三方支付、货到付款等。
- 数据压缩:无损压缩(如zip、gzip)和有损压缩(如jpeg、mp3)。
- 游戏AI:根据不同关卡和难度,切换敌人的行为策略或玩家的辅助策略。
七、优缺点
优点
- 可扩展性强:可以很容易地添加新的策略,只要实现抽象策略类的接口即可。例如在游戏中添加新的角色攻击方式,只需要创建一个新的具体策略类。
- 代码复用性高:具体策略类可以在多个环境类中复用。例如不同的游戏场景可能都用到战士的攻击策略。
- 符合开闭原则:对扩展开放,对修改关闭。当需要添加新策略时,不需要修改环境类和其他已有的策略类。
缺点
- 策略类数量增多:如果有很多策略,会导致策略类的数量增加,使得代码结构变得复杂,需要管理更多的类。
- 客户端必须了解策略差异:客户端(环境类)需要知道不同策略之间的区别,才能正确地选择和使用策略。例如在游戏角色选择攻击策略时,需要知道每个策略对应的攻击方式。