设计模式之(一)策略模式

本文首发于 我的个人博客

什么是策略模式

类型:行为类模式

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。——《Head First 设计模式》

场景

假设我们有个需求是,小孩和大人,小孩吃蔬菜,大人吃肉,小孩每天跑步一小时,大人每天跑步2小时,那么这个简单的需求怎么实现呢?

继承来实现

首先,我们想到的是继承,定义一个Person类,里么有两个方法

1
2
- (void)eatSomeThing;
- (void)runEveryDay;

然后,定义ChildPersonAdultPerson 都继承自Person。然后在各自的类中,分别实现,eatSomeThingrunEveryDay 的方法

这种方法是可以,但是,有个问题,就是代码重用性较差,例如,如果再来一个 老人,吃蔬菜,能跑2小时,那么实现起来的话,就是在定义一个类 OldPerson 继承Person ,也实现eatSomeThingrunEveryDay 的方法,但问题是,我们之前已经实现了,吃蔬菜的方法,所以没必要再写重复的代码。

这时候,就可以用到策略模式

策略模式来实现

run

  • 协议 Run.h
1
2
3
4
5
#import <Foundation/Foundation.h>

@protocol Run <NSObject>
-(void)runEveryDay;
@end
  • 定义类 RunOneHour 准守协议 <Run>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Cocoa/Cocoa.h>
#import "Run.h"

@interface RunOneHour : NSObject<Run>
- (void)runEveryDay;
@end


#import "RunOneHour.h"

@implementation RunOneHour
- (void)runEveryDay{
NSLog(@"我每天能跑一个小时");
}
@end
  • 定义类 RunTwoHours准守协议 <Run>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Cocoa/Cocoa.h>
#import "Run.h"

@interface RunTwoHours : NSObject<Run>
-(void)runEveryDay;
@end

#import "RunTwoHours.h"

@implementation RunTwoHours
- (void)runEveryDay{
NSLog(@"我每天能跑两个小时");
}
@end

eat

  • 协议 Eat
1
2
3
4
5
6
#import <Foundation/Foundation.h>

@protocol Eat <NSObject>

- (void)eatSomeThing;
@end
  • 定义类 EatMeat准守协议 <Eat>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import "Eat.h"

@interface EatMeat : NSObject<Eat>
- (void)eatSomeThing;
@end


#import "EatMeat.h"

@implementation EatMeat
- (void)eatSomeThing{
NSLog(@"我吃肉");
}
@end
  • 定义类 EatVegetables准守协议 <Eat>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
#import "Eat.h"

@interface EatVegetables : NSObject<Eat>
- (void)eatSomeThing;
@end



#import "EatVegetables.h"

@implementation EatVegetables
- (void)eatSomeThing{
NSLog(@"我吃蔬菜");
}
@end

person

Person有两个属性,实现了<Eat>协议的eat属性 和 实现了<Run>协议的run属性
两个方法,分别是,eatSomeThingrunEveryDay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <Foundation/Foundation.h>
#import "Eat.h"
#import "Run.h"
@interface Person : NSObject

@property (nonatomic,strong) id<Eat> eat;
@property (nonatomic,strong) id<Run> run;
- (void)eatSomeThing;
- (void)runEveryDay;
@end



#import "Person.h"

@implementation Person
- (void)eatSomeThing{
[self.eat eatSomeThing];
}
- (void)runEveryDay{
[self.run runEveryDay];
}
@end
  • ChildPerson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "Person.h"
@interface ChildPerson : Person
@end


#import "ChildPerson.h"
#import "EatVegetables.h"
#import "RunOneHour.h"
@implementation ChildPerson

- (instancetype)init
{
self = [super init];
if (self) {
self.eat = [[EatVegetables alloc] init];
self.run = [[RunOneHour alloc] init];
NSLog(@"我是小孩");
}
return self;
}
@end
  • AdultPerson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "Person.h"
@interface AdultPerson : Person
@end


#import "AdultPerson.h"
#import "EatMeat.h"
#import "RunTwoHours.h"

@implementation AdultPerson
- (instancetype)init
{
self = [super init];
if (self) {
self.eat = [[EatMeat alloc] init];
self.run = [[RunTwoHours alloc] init];
NSLog(@"我是大人");
}
return self;
}
@end

使用

1
2
3
4
5
6
7
Person *child = [[ChildPerson alloc] init];
[child eatSomeThing];
[child runEveryDay];

Person *adult = [[AdultPerson alloc] init];
[adult eatSomeThing];
[adult runEveryDay];

输出

1
2
3
4
5
6
2019-07-08 08:10:41.159747+0800 iOS-策略模式[1402:13333] 我是小孩
2019-07-08 08:10:41.160257+0800 iOS-策略模式[1402:13333] 我吃蔬菜
2019-07-08 08:10:41.160300+0800 iOS-策略模式[1402:13333] 我每天能跑一个小时
2019-07-08 08:10:41.160343+0800 iOS-策略模式[1402:13333] 我是大人
2019-07-08 08:10:41.160379+0800 iOS-策略模式[1402:13333] 我吃肉
2019-07-08 08:10:41.160396+0800 iOS-策略模式[1402:13333] 我每天能跑两个小时

这样就用策略模式实现了这个需求,如果这时候,增加一个需求 如果再来一个 老人,吃蔬菜,能跑2小时

只需要再定义一个类 OldPerson 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Person.h"
@interface OldPerson : Person
@end

#import "OldPerson.h"
#import "RunTwoHours.h"
#import "EatVegetables.h"
@implementation OldPerson
- (instancetype)init
{
self = [super init];
if (self) {
self.eat = [[EatVegetables alloc] init];
self.run = [[RunTwoHours alloc] init];
NSLog(@"我是老人");
}
return self;
}
@end

调用时候 只需要如下

1
2
3
Person *old = [[OldPerson alloc] init];
[old eatSomeThing];
[old runEveryDay];

即可输出

1
2
3
2019-07-08 08:46:16.007758+0800 iOS-策略模式[2567:27396] 我是老人
2019-07-08 08:46:16.007770+0800 iOS-策略模式[2567:27396] 我吃蔬菜
2019-07-08 08:46:16.007780+0800 iOS-策略模式[2567:27396] 我每天能跑两个小时

根本不需要重写吃,和跑的方法。

总结

优点

  • 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
  • 提供了可以替换继承关系的办法
  • 策略模式的Stategy类层次为Context定义了一些列的可供重用的算法或行为。继承有助于析取出算法中的公共功能。
  • 策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  • 易于扩展,增加一个新的策略对策略模式来说非常容易,基本上可以在不改变原有代码的基础上进行扩展

缺点

  • 维护各个策略类会给开发带来额外开销,一般来说,策略类的数量超过6个,就比较麻烦
  • 必须对客户端(调用者)暴露所有的策略类,因为使用哪种策略是由客户端来决定的,因此,客户端应该知道有什么策略,并且了解各种策略之间的区别,否则,后果很严重。

改进

  • 策略模式是一种简单常用的模式,我们在进行开发的时候,会经常有意无意地使用它,一般来说,策略模式不会单独使用,跟模版方法模式、工厂模式等混合使用的情况比较多。

Demo地址