什么是策略模式(Strategy Pattern)

策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。这属于行为型模式。
想象一下你要出门旅行(客户端 Client),目的地确定后(上下文 Context),你需要选择交通方式(策略 Strategy):可以开车去(具体策略 A ConcreteStrategyA),可以坐火车(具体策略 B ConcreteStrategyB),也可以坐飞机(具体策略 C ConcreteStrategyC)。每种交通方式都是一种独立的策略,你可以根据具体情况(如时间、预算、距离)选择最合适的策略,而选择哪种策略并不会改变旅行的目的地这个上下文。
策略模式的核心在于将算法(行为)封装在独立的对象中,使得它们可以轻松互换。

核心思想

  1. 定义策略接口 (Strategy): 定义一个公共接口,它是所有支持的算法(策略)的通用接口。这个接口通常声明一个或多个方法,代表算法需要执行的操作。
  1. 创建具体策略 (ConcreteStrategy): 实现 Strategy 接口。每个具体策略类封装了一个具体的算法或行为。
  1. 定义上下文 (Context):
      • 持有一个对 Strategy 接口的引用(strategy 成员变量)。
      • 维护一个对 Strategy 对象的引用。可以通过构造函数注入或提供一个 setStrategy 方法来在运行时改变策略。
      • Context 类的方法(例如 executeStrategy() 或 performOperation())会将具体的算法执行委托给当前持有的 Strategy 对象。Context 本身不实现算法逻辑,只负责调用策略接口。
  1. 客户端 (Client):
      • 创建具体的 Strategy 对象。
      • 创建一个 Context 对象。
      • 将具体的 Strategy 对象设置给 Context 对象。
      • 调用 Context 对象的方法来执行选定策略的算法。

优点

  1. 算法自由切换 (Algorithm Interchangeability): 可以在运行时轻松地切换算法,提供了极大的灵活性。
  1. 消除条件语句 (Eliminates Conditional Statements): 避免了在 Context 中使用大量的 if/else 或 switch/when 语句来选择不同的行为,使得 Context 代码更简洁,更容易维护。
  1. 封装性好 (Encapsulation): 每个算法都被封装在独立的策略类中,易于理解、测试和维护。符合单一职责原则。
  1. 易于扩展 (Extensibility): 添加新的算法只需要增加一个新的 ConcreteStrategy 类实现 Strategy 接口即可,无需修改 Context 类或现有策略类。符合开闭原则。
  1. 复用性 (Reusability): 策略可以被多个 Context 复用。

可能的缺点

  1. 类数量增加: 每个策略都需要一个单独的类,如果策略很多,会导致类的数量增加。
  1. 客户端必须了解所有策略: 客户端通常需要知道有哪些可用的策略,以便能够选择并设置合适的策略给 Context。(有时 Context 也可以根据配置或其他条件自行选择策略,对客户端隐藏细节)。
  1. 增加了对象间的交互: Context 和 Strategy 之间需要通信,Context 可能需要将自身(this)或相关数据传递给 Strategy 对象,这增加了对象间的通信复杂度。

使用场景

  1. 当你有多种方式(算法)来完成同一任务,并且希望在运行时根据情况选择其中一种时。
  1. 当一个类定义了多种行为,并且这些行为以多个条件语句的形式出现时,可以将每种行为封装到一个独立的策略类中。
  1. 当你希望算法的实现细节与使用算法的客户端代码分离时。
  1. 当你想让一个类的不同行为仅仅是算法实现上的不同时。
  1. 数据排序: 不同的排序算法(快速排序、归并排序、冒泡排序)可以作为不同的策略。Collections.sort() 方法接受一个 Comparator 对象,Comparator 就是一种策略接口的体现。
  1. 数据验证: 对用户输入进行不同的验证规则(非空、邮箱格式、密码强度)可以作为不同的策略。
  1. 支付方式: 在线购物时选择不同的支付方式(信用卡、支付宝、微信支付)可以看作不同的策略。
  1. 压缩/解压: 使用不同的压缩算法(ZIP, GZIP, RAR)可以实现为不同的策略。
  1. Android 中的场景:
      • RecyclerView 的 LayoutManager: 虽然不是严格的策略模式(LayoutManager 职责更重),但其思想类似。不同的 LayoutManager(LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager)定义了不同的布局算法(策略),你可以轻松地为 RecyclerView(Context)切换不同的 LayoutManager 来改变其布局方式。
      • 动画插值器 (Interpolator): Android 动画系统中的 Interpolator(如 LinearInterpolator, AccelerateDecelerateInterpolator, BounceInterpolator)定义了动画数值如何随时间变化的算法(策略)。你可以为同一个动画(Context)设置不同的 Interpolator 来改变其运动效果。
      • 图片加载库中的缓存策略: 像 Glide 或 Picasso 这样的库内部可能使用不同的策略来决定是从内存缓存、磁盘缓存还是网络加载图片。
      • 表单提交/数据同步: 可以根据网络状况(Wi-Fi/移动数据)选择不同的数据同步策略(立即同步/延迟同步/仅 Wi-Fi 同步)。
      • 自定义 View 的绘制或行为: 根据 View 的状态或配置,选择不同的绘制策略或触摸事件处理策略。

UML 图

下面是基于“支付方式”示例的策略模式 UML 图:
  • PaymentStrategy (Strategy): 定义了支付操作的接口 pay()。
  • CreditCardPayment, PayPalPayment, CryptoPayment (ConcreteStrategy): 实现了 PaymentStrategy 接口,各自封装了不同的支付逻辑。
  • ShoppingCart (Context): 持有一个 PaymentStrategy 的引用 paymentStrategy。它的 checkout() 方法会调用 paymentStrategy.pay() 来完成支付,具体使用哪种支付方式由当前设置的 paymentStrategy 决定。它提供了 setPaymentStrategy() 方法来切换策略。
  • Client: 创建 ShoppingCart 和具体的支付策略实例,并将选定的策略设置给购物车,最后调用 checkout()。

代码示例

在这个例子中:
  • PaymentStrategy 是策略接口。
  • CreditCardPayment, PayPalPayment, CryptoPayment 是具体策略,实现了不同的支付逻辑。
  • ShoppingCart 是上下文,它包含商品列表,并持有一个 PaymentStrategy 引用。checkout 方法委托给当前的支付策略。
  • 客户端代码创建了购物车和各种支付策略,并通过 setPaymentStrategy 动态地改变购物车的支付方式,然后调用 checkout 执行支付。

总结

  • 策略模式提供了一种优雅的方式来封装和管理不同的算法(行为),并使得它们可以在运行时相互替换。
  • 它通过委托将算法的实现从使用算法的上下文中分离出来,避免了复杂的条件逻辑,提高了代码的灵活性、可扩展性和可维护性。
  • 在 Android 开发中,当你需要根据不同条件应用不同逻辑(如布局、动画、验证、数据处理)时,策略模式是一个非常有用的工具。
💡
记住它的核心:定义算法族,分别封装,自由替换,委托执行。
 
设计模式——行为型-迭代器模式设计模式——行为型-责任链模式