iOS-策略模式
在实际开发过程中,app需求都是由产品那边给出,往往是他给出第一版功能,我们写好代码后,会相应的给出第二版、第三版功能,而这些功能是在实际使用中,根据用户需求而不断增加的。如果在编码之初,我们并未认识到这一点,并未后续添加的代码做好相应的设计准备,那么无疑,这个项目代码会越来越乱,就会导致这样一个循环:
- 产品提需求
- 我根据需求写代码
- 产品增加需求
- 为了在规定时间内完成任务,我根据需要增加的需求增加代码(由于没有思考好相应的设计,使得代码又长又乱)
- 产品再增加需求
- 我再增加代码,由于前面代码设计不合理,使得即使只增加一个小小的功能,我整个项目各个地方都要添加这样的代码
- 产品觉得某个功能不好,要删掉,然后我得在项目中找到各种地方对应功能的代码,删掉,还得撸顺上下的逻辑关系
- 这样子,发现工作量好大、没时间看书、没时间学习,总是撸相同质量的代码,技能得不到提升
- 如果不从自己身上找原因,就开始骂产品,有事没事总增加需求,觉得呆在这公司没意思,然后辞职......
- 下一家公司,又开始这样的循环......
在知乎上看到这样一个问答:
是不是程序员都感觉不幸福?过得很不好?因为不管bat公司还是中小公司,各种加班、天天撸代码,没时间谈女朋友.......
看到这样一个很赞的回答:技术好的,都过得不错,技术差的......
- 现在有这样一个任务,做一个商场收银软件,营业员根据客户所购买的商品的单价和数量,向客户收费。
很简单的一个需求,稍微分析就可以得到下图的界面:

根据界面,也可以很快的将主要代码写出来:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
#import <Foundation/Foundation.h>@interface ZYTotalPrices : NSObject@property (nonatomic, assign) double singlePrices;@property (nonatomic, assign) int number;- (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number;- (double)totalPrices;@end#import "ZYTotalPrices.h"@implementation ZYTotalPrices- (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number{ if (self = [super init]) { self.number = [number intValue]; self.singlePrices = [singlePrices doubleValue]; } return self;}- (double)totalPrices{ return self.number * self.singlePrices;}@end |
然后产品跑过来和你说,现在有新的需求了,商场促销,所有商品打八折。
想想,直接在- (double)totalPrices 方法里面,再乘以0.8就ok了......这样是满足了现有的需求,那要是某天商场打七折或者说,打完折回复原价,怎么办?
2. 增加打折
其实,只需要增加一个下拉选择框,让收银员选择当前的打折就可以了,主要代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
#import <Foundation/Foundation.h>@interface ZYTotalPrices : NSObject@property (nonatomic, assign) double singlePrices;@property (nonatomic, assign) int number;/** * 下拉菜单中的某个属性字符串 */@property (nonatomic, copy) NSString *itemStr;- (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number;- (double)totalPrices;@end#import "ZYTotalPrices.h"@interface ZYTotalPrices ()/** * 打折语句 */@property (nonatomic, strong) NSArray *discountStrs;/** * 打折的具体数值 */@property (nonatomic, strong) NSArray *discountNumbers;/** * 打折对应下标 */@property (nonatomic, assign) int index;@end@implementation ZYTotalPrices- (NSArray *)discountNumbers{ if (!_discountNumbers) { _discountNumbers = @[@(1), @(0.8), @(0.5)]; } return _discountNumbers;}- (NSArray *)discountStrs{ if (!_discountStrs) { _discountStrs = @[@"正常收费", @"打八折", @"打五折"]; // 默认为正常收费 self.index = 0; } return _discountStrs;}- (void)setItemStr:(NSString *)itemStr{ _itemStr = [itemStr copy]; [self.discountStrs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { if ([obj isEqualToString:itemStr]) { self.index = (int)idx; *stop = YES; } }];}- (instancetype)initWithSinglePrices:(NSString *)singlePrices number:(NSString *)number{ if (self = [super init]) { self.number = [number intValue]; self.singlePrices = [singlePrices doubleValue]; } return self;}- (double)totalPrices{ double tempNumber = [self.discountNumbers[self.index] doubleValue]; return self.number * self.singlePrices * tempNumber;}@end |
差不多就有了下面这样的界面:(请勿完全对照)

这时候,产品过来说,需要增加“满300送100”的活动。这完全是可以在类里多增加一个方法,来计算这个的。但是,这种方法并不好,事实上,
我们完全可以抽出一个抽象基类出来,基类里面有个方法,就是返回总的钱数,而各个不同算法的子类继承自这个基类,实现基类里面的方法,然后用工具类根据不同的需求调用不同的子类,这样的话,分工很明确,如果有什么特殊的需求,找到这个子类增加就ok
3. 简单工厂模式
需要注意的是,其实打八折、七折、六折...完全是可以用个变量来表示的,没必要衍生出很多子类,直接就是一个打折子类,内部确定打折的具体折数;同理,满**减**也是如此。
面向对象编程,并不是类越多越好,类的划分是为了封装,但划分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
打多少折,只是形式的不同,抽象分析出来,所有的打折算法是一样的,所有打折算法应该是一个类。同理,满**减**也是如此。
代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
抽象基类:#import <Foundation/Foundation.h>@interface ZYTotalPrices : NSObject@property (nonatomic, assign) double singlePrices;@property (nonatomic, assign) int number;/** * 如此,抽象类里面有一个方法声明,实现交给子类 * */- (double)totalPrices;@end#import "ZYTotalPrices.h"@implementation ZYTotalPrices@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
正常价格:#import "ZYTotalPrices.h"@interface ZYPricesNoraml : ZYTotalPrices@end#import "ZYPricesNoraml.h"@implementation ZYPricesNoraml/** * 正常收费 * */- (double)totalPrices{ return self.singlePrices * self.number;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
打折:#import "ZYTotalPrices.h"@interface ZYPricesDiscount : ZYTotalPrices/** * 打折率,为小数 */@property (nonatomic, assign) double moneyRebate;- (instancetype)initWithMoneyRebate:(double)moneyRebate;@end#import "ZYPricesDiscount.h"@implementation ZYPricesDiscount- (instancetype)initWithMoneyRebate:(double)moneyRebate{ if (self = [super init]) { self.moneyRebate = moneyRebate; } return self;}- (double)totalPrices{ return self.number * self.singlePrices * self.moneyRebate;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
返利:#import "ZYTotalPrices.h"@interface ZYPricesReturn : ZYTotalPrices/** * 返利条件 */@property (nonatomic, assign) double moneyCondition;/** * 返利值 */@property (nonatomic, assign) double moneyReturn;- (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn;@end#import "ZYPricesReturn.h"@implementation ZYPricesReturn- (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn{ if (self = [super init]) { self.moneyCondition = moneyCondition; self.moneyReturn = moneyReturn; } return self;}- (double)totalPrices{ double total = self.number * self.singlePrices; return total - (int)(total / self.moneyCondition) * self.moneyReturn;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
工具类:#import <Foundation/Foundation.h>@class ZYTotalPrices;@interface ZYPricesTool : NSObject+ (ZYTotalPrices *)createTotalPricesWithItemStr:(NSString *)itemStr;@end#import "ZYPricesTool.h"#import "ZYPricesNoraml.h"#import "ZYPricesDiscount.h"#import "ZYPricesReturn.h"static NSArray *_arrayStrs;@implementation ZYPricesTool+ (NSArray *)arrayStrs{ if (_arrayStrs == nil) { _arrayStrs = @[@"正常收费", @"满300返100", @"打八折"]; } return _arrayStrs;}+ (ZYTotalPrices *)createTotalPricesWithItemStr:(NSString *)itemStr{ [self arrayStrs]; __block int index = 0; [_arrayStrs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { if ([obj isEqualToString:itemStr]) { index = (int)idx; *stop = YES; } }]; switch (index) { case 0: return [[ZYPricesNoraml alloc] init]; break; case 1: return [[ZYPricesReturn alloc] initWithMoneyCondition:300 moneyReturn:100]; break; case 2: return [[ZYPricesDiscount alloc] initWithMoneyRebate:0.8]; break; } return nil;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
控制器内代码:#import "ViewController.h"#import "ZYPricesTool.h"#import "ZYPricesNoraml.h"#import "ZYPricesDiscount.h"#import "ZYPricesReturn.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. ZYTotalPrices *totalPrices = [ZYPricesTool createTotalPricesWithItemStr:@"打八折"]; totalPrices.number = 5; totalPrices.singlePrices = 6; NSLog(@"%lf",[totalPrices totalPrices]);}@end |
这次,不论产品跑过要求怎么修改,都可以很简单的实现了。比如说,要“满500返200”,直接在ZYPricesTool里面增加一个就好,算法完全不需要重新写。如果又有一种需求,"满100元返积分10点,一次购物到一定积分,赠送礼物",直接产生一个继承自ZYTotalPrices的子类,在里面写产生积分的算法,然后在工具类里面增加响应处理即可,其他已经处理了的类,完全不需要去改动。
但是,也是优缺点,虽然简单的工厂设计模式解决了这个问题,但是这个模式只是解决了对象的创建问题,而且由于工厂本身包含了所有的收费方式,商场是有可能经常性的更改打折额度和返利额度,每次维护和扩展收费方式都要更改这个工厂类,以至于代码需要重新编译、部署,那这种处理方式就显得比较糟糕了。面对算法的时常变动,应该有更好的设计方法......
4. 策略模式
策略模式定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没错,但是算法本身只是一种策略,最主要的是,这些算法本身是可以随时替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。
接下来就是策略模式代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#import <Foundation/Foundation.h>@interface ZYTotalPrices : NSObject@property (nonatomic, assign) double singlePrices;@property (nonatomic, assign) int number;/** * 如此,抽象类里面有一个方法声明,实现交给子类 * */- (double)totalPrices;@end#import "ZYTotalPrices.h"@implementation ZYTotalPrices@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#import "ZYTotalPrices.h"@interface ZYPricesNoraml : ZYTotalPrices@end#import "ZYPricesNoraml.h"@implementation ZYPricesNoraml/** * 正常收费 * */- (double)totalPrices{ return self.singlePrices * self.number;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#import "ZYTotalPrices.h"@interface ZYPricesDiscount : ZYTotalPrices/** * 打折率,为小数 */@property (nonatomic, assign) double moneyRebate;- (instancetype)initWithMoneyRebate:(double)moneyRebate;@end#import "ZYPricesDiscount.h"@implementation ZYPricesDiscount- (instancetype)initWithMoneyRebate:(double)moneyRebate{ if (self = [super init]) { self.moneyRebate = moneyRebate; } return self;}- (double)totalPrices{ return self.number * self.singlePrices * self.moneyRebate;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#import "ZYTotalPrices.h"@interface ZYPricesReturn : ZYTotalPrices/** * 返利条件 */@property (nonatomic, assign) double moneyCondition;/** * 返利值 */@property (nonatomic, assign) double moneyReturn;- (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn;@end#import "ZYPricesReturn.h"@implementation ZYPricesReturn- (instancetype)initWithMoneyCondition:(double)moneyCondition moneyReturn:(double)moneyReturn{ if (self = [super init]) { self.moneyCondition = moneyCondition; self.moneyReturn = moneyReturn; } return self;}- (double)totalPrices{ double total = self.number * self.singlePrices; return total - (int)(total / self.moneyCondition) * self.moneyReturn;}@end |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#import <Foundation/Foundation.h>@class ZYTotalPrices;@interface ZYPricesContent : NSObject@property (nonatomic, strong) ZYTotalPrices *totalPrice;/** * 必须根据相应的itemStr字符串创建对应的算法对象 * */- (instancetype)initWithItemStr:(NSString *)itemStr;- (double)getResult;@end#import "ZYPricesContent.h"#import "ZYTotalPrices.h"#import "ZYPricesNoraml.h"#import "ZYPricesDiscount.h"#import "ZYPricesReturn.h"@implementation ZYPricesContent- (instancetype)initWithItemStr:(NSString *)itemStr{ if (self = [super init]) { [self commitInit:itemStr]; } return self;}- (void)commitInit:(NSString *)itemStr{ if ([itemStr isEqualToString:@"正常收费"]) { self.totalPrice = [[ZYPricesNoraml alloc] init]; } else if ([itemStr isEqualToString:@"满300返100"]) { self.totalPrice = [[ZYPricesReturn alloc] initWithMoneyCondition:300 moneyReturn:100]; } else { self.totalPrice = [[ZYPricesDiscount alloc] initWithMoneyRebate:0.8]; }}- (double)getResult{ return [self.totalPrice totalPrices];}@end |
仔细看这一次的代码,会发现,和上面用工厂调用的代码基本没什么变化,只是将工具类,改成了ZYPricesReturn类,这个类包含ZYTotalPrices类,持有ZYTotalPrices对象,还有就是原本在工具类里面判断的,具体调用哪个算法对象,也在ZYPricesReturn类里面实现了,可以说,ZYPricesReturn类就是为了ZYTotalPrices服务的。
来看看viewController里面的代码,就可以看出很明显的区别,也可以了解策略模式的好处在哪:
先是,调用工具类的viewController代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#import "ViewController.h"#import "ZYPricesTool.h"#import "ZYPricesNoraml.h"#import "ZYPricesDiscount.h"#import "ZYPricesReturn.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. ZYTotalPrices *totalPrices = [ZYPricesTool createTotalPricesWithItemStr:@"打八折"]; totalPrices.number = 5; totalPrices.singlePrices = 6; NSLog(@"%lf",[totalPrices totalPrices]); }@end |
可以很明显的发现,这种方法实现的,viewController想要调用各种算法,那么就得包含各种算法的头文件,创建并持有各种算法对象。
那么,如果是策略模式实现的代码,viewController里面的代码是怎么样的呢:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#import "ViewController.h"#import "ZYPricesContent.h"#import "ZYTotalPrices.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; ZYPricesContent *content = [[ZYPricesContent alloc] initWithItemStr:@"满300返100"]; content.totalPrice.number = 5; content.totalPrice.singlePrices = 120; NSLog(@"%lf",[content getResult]);}@end |
可以看到,viewController只需要认识ZYTotalPrices与ZYPricesContent这两个类即可,不必去认识各种算法类,大大降低了耦合度。
回头来反思下策略模式,策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,他可以以相同的方式调用所有的算法,减少了各种算法类和使用各算法类之间的耦合。
策略模式就是用来封装算法的,但是在实际应用中,我们可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
iOS-策略模式的更多相关文章
- ios策略模式应用
策略模式应用大量应用于解决巨型switch-case if-else..... 具体使用方法 : 策略基类(BaseStrategy)包含一个虚算法,所有子类实现虚算法 容器类含有一个指向策略基类的 ...
- IOS之Objective-C学习 策略模式
对于策略模式,我个人理解策略模式就是对各种规则的一种封装的方法,而不仅仅是对算法的封装与调用而已.与工厂模式中简单工厂有点类似,但是比简单工厂更有耦合度,因为策略模式以相同的方法调用所有的规则,减少了 ...
- iOS设计模式之策略模式
策略模式(Strategy) 基本理解 面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类. 策略模式:它定义了算法家族,分别封装起来, ...
- iOS开发-策略模式
策略(Strategy)模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化.策略模式是对算法的包装,是把使用算法的责任和算法本身分割开 ...
- 设计模式之策略模式(iOS开发,代码用Objective-C展示)
在实际开发过程中,app需求都是由产品那边给出,往往是他给出第一版功能,我们写好代码后,会相应的给出第二版.第三版功能,而这些功能是在实际使用中,根据用户需求而不断增加的.如果在编码之初,我们并未认识 ...
- iOS架构模式浅析
这是以前旧博客在13年规划写的一个系列,写了一部分内容,还没有完成.现在重新整理编写.计划从基础知识六大设计原则.设计模式中类的关系开始,然后会对iOS开发中的常用架构模式进行介绍,最后对GoF的23 ...
- javascript设计模式:策略模式
前言 策略模式有效利用组合.委托.多态等技术和思想,可以有效避免多重条件选择语句. 策略模式对开放-封闭原则提供了很好的支持,将算法封装在strategy中,使得他们易于切换.理解.扩展. 策略模式中 ...
- StrategyPattern (策略模式)
/** * 策略模式 * @author TMAC-J * 根据环境的不同选择不同的策略,把策略用接口抽象出来 */ public class StrategyPattern { interface ...
- JAVA 设计模式之策略模式
定义:定义一组算法,将每个算法都封装起来,并且使他们之间可以互换. 类型:行为类模式 策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换.在前面说过 ...
- Java设计模式之策略模式(Strategy)
前言: 最近一直在学习基于okHttp网络请求,学习的过程中就想起了之前项目中有这么一个需求不同的接口要采用不同的加密方式,比如登录之前要采用RSA加密,登录之后要采用AES加密,当时是采用靠传递一个 ...
随机推荐
- spring-aop学习
SpringAOP学习 author:luojie 1. AOP中的基本概念 AOP的通用术语,并非spring java所特有.很遗憾AOP的术语不是特别的直观.但如果让Spring java来 ...
- 8天入门wpf(转)
8天入门wpf—— 第一天 基础概念介绍 8天入门wpf—— 第二天 xaml详解 8天入门wpf—— 第三天 样式 8天入门wpf—— 第四天 模板 8天入门wpf—— 第五天 数据绑定 8天入门w ...
- 转载:[转]如何学好3D游戏引擎编程
[转]如何学好3D游戏引擎编程 Albert 本帖被 gamengines 从 游戏引擎(Game Engine) 此文为转载,但是值得一看. 此篇文章献给那些为了游戏编程不怕困难的热血青年,它的 ...
- 用Java程序判断是否是闰年
我们知道,(1)如果是整百的年份,能被400整除的,是闰年:(2)如果不是整百的年份,能被4整除的,也是闰年.每400年,有97个闰年.鉴于此,程序可以作以下设计: 第一步,判断年份是否被400整除, ...
- Celery Running Environment
After running celery in my machine, I got this: Running a worker with superuser privileges when the ...
- Inno setup定制安装界面
Innosetup功能很强大,可以通过它提供的Wizard接口来定制界面,但我对PASCAL语言不熟悉,也不清楚通过那种接口可改动的范围有多大,最后做出来的效果是否好,所以选择了通过一个DLL来实现我 ...
- sql语句:插入的时候判断是否有重复项
把t_table1中数据插入到t_table中,同时确保t_table中不会有重复的项 insert into t_table (column1,column2,column3) select co ...
- javascript 设计模式-----工厂模式
所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...
- Java多线程18:线程池
使用线程池与不使用线程池的差别 先来看一下使用线程池与不适应线程池的差别,第一段代码是使用线程池的: public static void main(String[] args) { long sta ...
- Javascrpt无刷新文件上传
最近工作中遇到上传文件问题,主要需求是一步点击上传,兼容ie8+,当时用的dojox/form/uploader控件,这两天扒了一下源码,明白了原理拿出来分享一下. 总体思路如下: 1.对于支持XML ...