场景分析
我们在网上购买商品的时候,经常遇到各种打折优惠活动,不同的节假日或者时间优惠策略都不相同,如果让我们去实现,那么如何做呢?
常规做法是根据不同的优惠政策,使用if进行判断,写很多判断分支进行处理。类似下面这种。
if (正常价格) {
//具体优惠策略处理
}else if (七折优惠){
//具体优惠策略处理
}else if (国庆节优惠){
//具体优惠策略处理
}else if (优惠策略4条件){
//具体优惠策略处理
}else{
//具体优惠策略处理
}
这种方式虽然可以完成功能,但是缺点很多
- 优惠策略经常改动。那么每次改动都需要来修改这段代码块,违反开闭原则
- 如果优惠策略非常多,那么就需要写很多判断分支,判断复杂
- 需要优惠策略功能的客户程序如果直接包含换行优惠策略的代码的话将会变得复杂,这使得客户程序庞大并且难以维护, 尤其当其需要支持多种优惠策略算法时问题会更加严重
要想避免上面的问题,解决方案就是把每种优惠策略封装成单独的类,客户端需要使用哪个优惠策略,就切换到该策略即可。这就需要使用到我们今天讲解的策略模式,下面来具体看看。
定义
定义一系列算法,把它们一个个的封装起来,并且使他们可以相互替换。策略模式使得算法可独立于使用它的客户端而变化。
通过定义,我们可以发现策略模式完全就是为解决我们开头提到的问题而生的。文章开头的实现方式就是因为把算法耦合进了客户端才会导致一系列问题。策略模式就是把算法和客户端解耦,把算法独立出来,然后给这些算法提供一个抽象的接口,每个具体的算法去实现这个接口,客户端只需要面向接口编程,需要使用哪个具体的算法,就切换到该算法即可。这样以后无论如何修改、扩展算法,都不会影响客户端的实现。
下面就来看看具体的UML结构图
UML结构图及说明
注意到结构图里面引入了一个context上下文对象,这个对象主要是为了隔离客户端和具体算法。context持有一个具体的算法对象,然后调用这个具体算法。但是context把具体算法的选择交给客户端来执行,自己只是持有客户端选择的的具体算法,然后客户端调用context暴露的接口,context就去调用具体的算法执行功能。
这样客户端只需要动态切换算法,然后设置到context,就可以调用不同的算法。
代码实现
上面我们分析了如何使用策略模式来完成我们的需求,下面就来看看具体的代码实现。
1、定义算法接口
#import <Foundation/Foundation.h>
@protocol strategyInterface <NSObject>
-(NSInteger)calcPrice:(NSInteger)goodsPrice;
@end
2、具体算法实现
#import <Foundation/Foundation.h>
#import "strategy.h"
@interface NationalDayStrategy : NSObject<strategyInterface>
@end
===================
#import "NationalDayStrategy.h"
@implementation NationalDayStrategy
-(NSInteger)calcPrice:(NSInteger)goodsPrice{
return goodsPrice * 0.5 - 12;
}
@end
上面只实现了国庆节优惠策略,其他的策略类似,具体看demo。
3、实现context
#import <Foundation/Foundation.h>
#import "strategy.h"
@interface Price : NSObject
@property(strong,nonatomic)id<strategyInterface> strategy;
- (instancetype)initWithStrategy:(id<strategyInterface>)strategy;
- (NSInteger)quotePirce:(NSInteger)goodsPrice;
@end
===================================
#import "Price.h"
@implementation Price
- (instancetype)initWithStrategy:(id<strategyInterface>)strategy
{
self = [super init];
if (self) {
self.strategy = strategy;
}
return self;
}
-(NSInteger)quotePirce:(NSInteger)goodsPrice{
return [self.strategy calcPrice:goodsPrice];
}
@end
4、测试
id<strategyInterface> strategy = [NationalDayStrategy new];
Price *quote = [[Price alloc]initWithStrategy:strategy];
NSInteger quotePrice = [quote quotePirce:10002];
NSLog(@"处理后的商品价格为:%zd", quotePrice);
如果需要换成其他优惠策略,只需要做如下更改即可:
id<strategyInterface> strategy = [NationalDayStrategy new];
改成:
id<strategyInterface> strategy = [discountStrategy new];
5、说明
上面的实现过程,我们把本来的商品价格作为参数通过context传递到具体的算法。还有一种情况我们可以把context本身作为参数传递给具体算法,这样后者就可以在合适的情况回调context。
使用时机
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一
个类的方法。
- 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的
算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数
据结构。
- 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。
将相关的条件分支移入它们各自的 strategy 类中以代替这些条件语句。
优缺点
相 关 算 法 系 列 strategy类层次为 context 定 义 了 一 系 列 的 可 供 重 用 的 算 法 或 行 为 。 可以使用继承提取出这些算法中的公共功能。
一个替代继承的方式。你可以使用继承提供另一种支持多种算法或行为的方法,首先直接生成一个 context 类 的 子 类 , 然后 给 它 以 不 同 的 行 为,但 这 会 将 行 为 硬 行 编 制 到context 中。而将 算法的实现与context的实现混合起来 , 从而使 context 难 以 理 解 、 难 以 维 护 和 难 以 扩 展 , 而 且 还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别只是它们所使用的算法或行为的不同。而将算法封装在独立的 strategy 类中,使得你可以独立于其context改变它,使它易于切换、 易于理解、易于扩展。
消除了一些条件语句。strategy模 式 提 供 了 用 条 件 语 句 选 择 所 需 的 行 为 以 外 的 另 一 种 选 择。当不同的行为堆砌在一个类中时 , 很难避免使用条件语句来选择合适的行为。将行为封装 在一个个独立的 strategy 类 中 消 除 了 这 些 条 件 语 句 。
实现的选择 S t r a t e g y 模式可以提供相同行为的不同实现。客户可以根据不同时间 / 空间 权衡取舍要求从不同策略中进行选择。
客户必须了解不同的 S t r a t e g y。 本 模 式 有 一 个 潜 在 的 缺 点 , 就 是 一 个 客 户 要 选 择 一 个 合 适的 S t r a t e g y 就必须知道这些 S t r a t e g y 到 底 有 何 不 同 。 此 时 可 能 不 得 不 向 客 户 暴 露 具 体 的 实 现 问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用 S t r a t e g y 模式。
- 增加了对象的数目 。你需要为每个不同的具体的S t r a t e g y增加了一个新的类,这就增加了应用中的对象的数目。此时你可以将 S t r a t e g y 实 现为可供各 C o n t e x t共享的无状态的对象来减少这一开销。任何其余的状态都由 C o n t e x t 维护。C o n t e x t 在每一次对 S t r a t e g y 对象的请求中都将这个状态传递过去。共享的 S t r a g e y不应在各次 调用之间维护状态。 F l y w e i g h t 模式更详细地描述了这一方法。