什么是策略模式(Strategy Pattern)
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。这属于行为型模式。
想象一下你要出门旅行(客户端 Client),目的地确定后(上下文 Context),你需要选择交通方式(策略 Strategy):可以开车去(具体策略 A ConcreteStrategyA),可以坐火车(具体策略 B ConcreteStrategyB),也可以坐飞机(具体策略 C ConcreteStrategyC)。每种交通方式都是一种独立的策略,你可以根据具体情况(如时间、预算、距离)选择最合适的策略,而选择哪种策略并不会改变旅行的目的地这个上下文。
策略模式的核心在于将算法(行为)封装在独立的对象中,使得它们可以轻松互换。
核心思想
- 定义策略接口 (Strategy): 定义一个公共接口,它是所有支持的算法(策略)的通用接口。这个接口通常声明一个或多个方法,代表算法需要执行的操作。
- 创建具体策略 (ConcreteStrategy): 实现 Strategy 接口。每个具体策略类封装了一个具体的算法或行为。
- 定义上下文 (Context):
- 持有一个对 Strategy 接口的引用(strategy 成员变量)。
- 维护一个对 Strategy 对象的引用。可以通过构造函数注入或提供一个 setStrategy 方法来在运行时改变策略。
- Context 类的方法(例如 executeStrategy() 或 performOperation())会将具体的算法执行委托给当前持有的 Strategy 对象。Context 本身不实现算法逻辑,只负责调用策略接口。
- 客户端 (Client):
- 创建具体的 Strategy 对象。
- 创建一个 Context 对象。
- 将具体的 Strategy 对象设置给 Context 对象。
- 调用 Context 对象的方法来执行选定策略的算法。
优点
- 算法自由切换 (Algorithm Interchangeability): 可以在运行时轻松地切换算法,提供了极大的灵活性。
- 消除条件语句 (Eliminates Conditional Statements): 避免了在 Context 中使用大量的 if/else 或 switch/when 语句来选择不同的行为,使得 Context 代码更简洁,更容易维护。
- 封装性好 (Encapsulation): 每个算法都被封装在独立的策略类中,易于理解、测试和维护。符合单一职责原则。
- 易于扩展 (Extensibility): 添加新的算法只需要增加一个新的 ConcreteStrategy 类实现 Strategy 接口即可,无需修改 Context 类或现有策略类。符合开闭原则。
- 复用性 (Reusability): 策略可以被多个 Context 复用。
可能的缺点
- 类数量增加: 每个策略都需要一个单独的类,如果策略很多,会导致类的数量增加。
- 客户端必须了解所有策略: 客户端通常需要知道有哪些可用的策略,以便能够选择并设置合适的策略给 Context。(有时 Context 也可以根据配置或其他条件自行选择策略,对客户端隐藏细节)。
- 增加了对象间的交互: Context 和 Strategy 之间需要通信,Context 可能需要将自身(this)或相关数据传递给 Strategy 对象,这增加了对象间的通信复杂度。
使用场景
- 当你有多种方式(算法)来完成同一任务,并且希望在运行时根据情况选择其中一种时。
- 当一个类定义了多种行为,并且这些行为以多个条件语句的形式出现时,可以将每种行为封装到一个独立的策略类中。
- 当你希望算法的实现细节与使用算法的客户端代码分离时。
- 当你想让一个类的不同行为仅仅是算法实现上的不同时。
- 数据排序: 不同的排序算法(快速排序、归并排序、冒泡排序)可以作为不同的策略。Collections.sort() 方法接受一个 Comparator 对象,Comparator 就是一种策略接口的体现。
- 数据验证: 对用户输入进行不同的验证规则(非空、邮箱格式、密码强度)可以作为不同的策略。
- 支付方式: 在线购物时选择不同的支付方式(信用卡、支付宝、微信支付)可以看作不同的策略。
- 压缩/解压: 使用不同的压缩算法(ZIP, GZIP, RAR)可以实现为不同的策略。
- 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 开发中,当你需要根据不同条件应用不同逻辑(如布局、动画、验证、数据处理)时,策略模式是一个非常有用的工具。
记住它的核心:定义算法族,分别封装,自由替换,委托执行。
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/1dfece13-c67b-44d6-8024-09699f34db60
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts