需求:最近在视频播放中,遇到了播放器来回切换调试的问题,那么接下来,会介绍一下怎么做才会使代码更加适用于业务且改动较小的方案。
场景一
- 只有腾讯播放SDK
方案一
直接在ViewController
初始化TencentSDK
,并实现对应的播放
、暂停
、停止
、重置
等功能。
- (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
,在原来的业务逻辑里面替换代码。
- (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
作为播放器的中间层,对之前的方案一进行更改
- (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
做更改,上层业务不需要更改。
- (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
代码如下
- (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
协议,协议有play
、pause
、stop
、resume
等通用method
@protocol PlayProtocol
- (void)play;
- (void)pause;
- (void)stop;
- (void)resume;
创建TencentPlayObject
类,遵守PlayProtocol
协议,在TencentPlayObject
初始化TencentSDK
,并实现PlayProtocol
协议制定的方法。
- (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];
}
创建SinaPlayObject
和YouKuObject
,遵守PlayProtocol
,初始化相对应的SDK,并实现协议方法,方法同TencentPlayObject
一样。
创建PlayerFactory
类,根据type
类型,用来生产对应的playObject
@implementation PlayerFactory
- (instancetype)initWithType:(NSNumber *)type {
if (self = [super init]) {
_type = type;
}
return self;
}
- (id)productionPlayer {
if ([self.type isEqualToNumber:@1]) {
return self.tencentPlayer;
} else if ([self.type isEqualToNumber:@2]) {
return self.sinaPlayer;
} else {
return self.youkuPlayer;
}
}
接下来对ViewController
的代码进行调整
@interface ViewController ()
@property (nonatomic , strong) id 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
业务层的代码也保持了不变的原则。如果需要删除哪个播放器,只要对应删除SDK
和Object
类即可,不用像在中间层一样在代码中删除,出错机率也大大降低。
如果在使用协议方案的同时再加一层中间层会怎样?
可以把协议比作一套算法,上层业务直接与中间层关联,在中间层可以去替换不同协议,就做到了算法上的改变,这种方案使程序结构更加严谨,替换方案更多,维护更加方便简洁。
项目 demo
我的博客 摆渡屋