策略模式在播放器调试中的运用

需求:最近在视频播放中,遇到了播放器来回切换调试的问题,那么接下来,会介绍一下怎么做才会使代码更加适用于业务且改动较小的方案。

场景一

  • 只有腾讯播放SDK

    方案一

    直接在ViewController初始化TencentSDK,并实现对应的播放暂停停止重置等功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];
self.tencentSDK = [TencentSDK new];
}

- (void)onActionPlay:(id)sender {
[self.tencentSDK play];
}

- (void)onActionPause:(id)sender {
[self.tencentSDK pause];
}

- (void)onActionStop:(id)sender {
[self.tencentSDK stop];
}

- (void)onActionResume:(id)sender {
[self.tencentSDK resume];
}

这种使用方式是我刚进入职场使用的一种傻瓜式方案,开发快速,简单明了,适用于一次性开发,确定播放类型,且后期不会再改动的代码。

场景二

  • 腾讯播放SDK
  • 新浪播放SDK
  • 最开始使用腾讯SDK,后面由于业务需求,接入新浪SDK

方案一

ViewController里面初始化TencentSDK。因为业务更改,ViewController里面初始化SinaSDK,在原来的业务逻辑里面替换代码。

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
- (void)viewDidLoad {
[super viewDidLoad];
// self.tencentSDK = [TencentSDK new];
self.sinaSDK = [SinaSDK new];
}

- (void)onActionPlay:(id)sender {
// [self.tencentSDK play];
[self.sinaSDK play];
}

- (void)onActionPause:(id)sender {
// [self.tencentSDK pause];
[self.sinaSDK play];
}

- (void)onActionStop:(id)sender {
// [self.tencentSDK stop];
[self.sinaSDK stop];
}

- (void)onActionResume:(id)sender {
// [self.tencentSDK resume];
[self.sinaSDK resume];
}

这种方案也可以完成我们更改播放SDK的需求,但是一个播放界面的业务不仅仅是播放暂停停止重置等功能,还会涉及到弹幕礼物分享等其他业务,随着业务增多,ViewController的代码也会随之变大,有时候我们在执行暂停时还会有其它操作,这样频繁去更改代码的行为并不友好,也增加了我们的工作量,出错机率也会随之增大。

方案二

工作中我们在使用第三方框架时,提倡加一层中间层,这样在替换第三方框架时,可以减少业务代码的更改,只需要中间层替换底层代码,保持上层业务代码不变。

创建PlayerInter作为播放器的中间层,对之前的方案一进行更改

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
- (instancetype)initWithType:(NSNumber *)type {
if (self = [super init]) {
_type = type;
}
return self;
}

- (void)interPlay {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK play];
} else {
[self.tencentSDK play];
}
}

- (void)interPause {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK pause];
} else {
[self.tencentSDK pause];
}
}

- (void)interStop {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK stop];
} else {
[self.tencentSDK pause];
}
}

- (void)interResume {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK resume];
} else {
[self.tencentSDK resume];
}
}

播放器SDK懒加载,根据PlayerInter初始化传进来的type类型,进行对应的播放器使用。ViewController的代码属于上层业务,当播放更换时,只需要PlayerInter做更改,上层业务不需要更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];
self.playerInter = [[PlayerInter alloc]initWithType:@1];
}

- (void)onActionPlay:(id)sender {
[self.playerInter interPlay];
}

- (void)onActionPause:(id)sender {
[self.playerInter interPause];
}

- (void)onActionStop:(id)sender {
[self.playerInter interStop];
}

- (void)onActionResume:(id)sender {
[self.playerInter interResume];
}

加了中间层,感觉方案上已经尽善尽美,当播放器更换时,只需要修改type类型即可,并且也可以做到后台控制使用哪种播放器。可是作为一个居安思危的程序员,我怎么能在这个时候就红枣枸杞茶喝起来呢?我设想如果再有一种新的播放器的话代码会怎么样。。。

  • 假设新增优酷播放SDK

PlayerInter代码如下

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
- (instancetype)initWithType:(NSNumber *)type {
if (self = [super init]) {
_type = type;
}
return self;
}

- (void)interPlay {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK play];
} else if ([self.type isEqualToNumber:@2]) {
[self.tencentSDK play];
} else {
[self.youkuSDK play];
}
}

- (void)interPause {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK pause];
} else if ([self.type isEqualToNumber:@2]) {
[self.tencentSDK pause];
} else {
[self.youkuSDK pause];
}
}

- (void)interStop {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK stop];
} else if ([self.type isEqualToNumber:@2]) {
[self.tencentSDK pause];
} else {
[self.youkuSDK stop];
}
}

- (void)interResume {
if ([self.type isEqualToNumber:@1]) {
[self.sinaSDK resume];
} else if ([self.type isEqualToNumber:@2]) {
[self.tencentSDK resume];
} else {
[self.youkuSDK stop];
}
}

除了频繁的if else改动之外,ViewController的上层业务代码没有做出任何改变,这种中间层的方案也还是可以解决我刚才设想的问题,可是作为一个有强迫症的程序员,if else过多也觉得会增加错误机率,如果接下来有第四、第五个播放器呢?

方案三

播放器功能

  • 播放
  • 暂停
  • 停止
  • 重置

每个播放器都不外乎有以上几个功能,那么是否可以通过一种新的方法来改变之前的策略呢?

创建PlayProtocol协议,协议有playpausestopresume等通用method

1
2
3
4
5
@protocol PlayProtocol <NSObject>
- (void)play;
- (void)pause;
- (void)stop;
- (void)resume;

创建TencentPlayObject类,遵守PlayProtocol协议,在TencentPlayObject初始化TencentSDK,并实现PlayProtocol协议制定的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (instancetype)init {
if (self = [super init]) {
//初始化SDK
_tencentSDK = [[TencentSDK alloc]init];
}
return self;
}

#pragma mark - PlayProtocol
- (void)play {
[self.tencentSDK play];
}

- (void)pause {
[self.tencentSDK pause];
}

- (void)stop {
[self.tencentSDK stop];
}

- (void)resume {
[self.tencentSDK resume];
}

创建SinaPlayObjectYouKuObject,遵守PlayProtocol,初始化相对应的SDK,并实现协议方法,方法同TencentPlayObject一样。

创建PlayerFactory类,根据type类型,用来生产对应的playObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation PlayerFactory

- (instancetype)initWithType:(NSNumber *)type {
if (self = [super init]) {
_type = type;
}
return self;
}

- (id<PlayProtocol>)productionPlayer {
if ([self.type isEqualToNumber:@1]) {
return self.tencentPlayer;
} else if ([self.type isEqualToNumber:@2]) {
return self.sinaPlayer;
} else {
return self.youkuPlayer;
}
}

接下来对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
25
26
27
@interface ViewController ()
@property (nonatomic , strong) id<PlayProtocol> player;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
PlayerFactory *factory = [[PlayerFactory alloc]initWithType:@2];
self.player = [factory productionPlayer];
}

- (void)onActionPlay:(id)sender {
[self.player play];
}

- (void)onActionPause:(id)sender {
[self.player pause];
}

- (void)onActionStop:(id)sender {
[self.player stop];
}

- (void)onActionResume:(id)sender {
[self.player resume];
}

相比较之前的中间层方案,协议的方法更直接明了,从PlayerFactory类中可以看出,只有在生产player的时候用到了if else判断,其它只要各自遵守协议即可,代码上更简单流程,出错机率大大降低。ViewController业务层的代码也保持了不变的原则。如果需要删除哪个播放器,只要对应删除SDKObject类即可,不用像在中间层一样在代码中删除,出错机率也大大降低。

如果在使用协议方案的同时再加一层中间层会怎样?

可以把协议比作一套算法,上层业务直接与中间层关联,在中间层可以去替换不同协议,就做到了算法上的改变,这种方案使程序结构更加严谨,替换方案更多,维护更加方便简洁。

项目 demo
我的博客 摆渡屋

------ 本文结束 ------