阅读 264

设计模式|基于责任链模式优化视频播放请求

Group 8.png

了解概念的同学可以直接跳转我的使用记录

概念:

属于行为模式的一种。
可以将请求沿着由处理者组成的进行发送,链上的各个处理者收到请求后,可以对请求进行处理,或者传递给下一个处理者。

解释:

链,指单链表,每一个元素通过后继指针与下一个元素相连,而这里的每一个元素都是封装了处理各自任务(责任)的对象。
每一个处理者用一个成员变量保存下一个处理者从而连接形成一条链。将事件请求先传给链的某个元素,然后事件请求就能沿着链的顺序传递。 接收到请求的处理者可以有多种处理方式:

  • 处理自己的任务后可以选择不继续传递,取消后续的处理步骤;
  • 处理完自己的任务,传给链上的下一个对象;
  • 不处理,直接传给下一个对象。

因此有可能请求传递到尾部都没被处理,或者传递到某一个就中断了。

例如比较经典的请求处理方式:图形界面中的事件处理。
拿 iOS 来举个例子: 响应者链条,做 iOS 的同学都应该很熟悉,简单来讲,点击屏幕,hit-testing 找到第一响应者后,事件会先发送给第一响应者,每一个响应者有一个 next 指针指向下一个响应者,先收到事件的响应者如果自己可以处理,那么就可以拦截事件,自己处理不往下传递。如果自己不处理,可以通过 next 指针传递给下一个响应者。事件有可能传递到最后都没有被处理。

角色:

从上面的解释可以看出来,核心的角色就是处理者。
-- 处理者要组成链,需要有一个成员变量记录下一个处理者。
-- 还需要有一个负责组装链的管理者来设置每个处理者的next对象。
-- 为此,处理者需要提供设置下一个处理者的接口给管理者使用。
-- 处理者需要能接收请求,那么也应该提供接收请求触发处理的方法
⚠ 按照依赖倒置,每个处理者都应该用相同的接口,那么就不需要产生具体的类的依赖。

  • 处理者(通用接口):

例如:

protocol Handler: AnyObject {

    var nextHandler: Handler? { get set }
    
    /// 设置下一个处理者
    @discardableResult
    func setNext(handler: Handler) -> Handler
    
    /// 接收处理请求
    func handle(request: String)    
}
复制代码
  • 基础处理者(可选):
    如果有共用逻辑,增加一个基础处理者来处理共用逻辑,可以减少代码重复。也可以写默认的事件处理传递逻辑。

  • 具体处理者:
    封装事件的处理,接收到请求后,决定是否处理事件,是否继续向链的下一个传递请求。

  • 管理者:
    组装链(如果有需要,也可以加动态修改链的逻辑),请求可发给任意一个处理者,而不一定是第一个

tips:可以结合简单工厂来组装链,也可以用工具类根据配置来创建链

适用场景:

某类请求需要经过多个步骤依次处理,这些步骤有先后顺序,或者说需要可以在运行时动态增删处理步骤,或者修改顺序。

优点 & 缺点:

优点:

发送者和具体接收者解耦;
可以在运行时动态控制请求处理的顺序;
符合单一职责原则;
符合开闭原则,不需要更改现有代码(不更改if else),给程序增加处理者。让代码更健壮。

缺点:

不能保证请求一定会被执行,如果每个处理者都不处理它的话,就到了链尾端以外。(不一定是缺点,看你使用的具体需求)

示例

⚠️:示例不是模版,只是为了进一步说明这种思想,仅供参考,使用时根据实际需求灵活变动。

示例用的swift:

/// 通用接口
protocol Handler: AnyObject {

    /// 下一个处理者
    var nextHandler: Handler? { get set }
    
    /// 设置下一个处理者
    @discardableResult
    func setNext(handler: Handler) -> Handler

    /// 传入请求
    func handle(request: String)
}

/// 提供默认实现
extension Handler {
    
    @discardableResult
    func setNext(handler: Handler) -> Handler {
        self.nextHandler = handler
        return handler
    }

    // 默认不处理,传个下一个
    func handle(request: String) {
       nextHandler?.handle(request: request)
    }
}

/// 具体的处理者,封装处理某一个步骤
class ConcreteHandler1: Handler {

    var nextHandler: Handler?

    func handle(request: String) {
        print("ConcreteHandler1 处理request")
        // 传递请求给下一个处理者
        nextHandler?.handle(request: request)
    }
}

class ConcreteHandler2: Handler {

    var nextHandler: Handler?

    func handle(request: String) {
        print("ConcreteHandler2 处理request")
        nextHandler?.handle(request: request)
    }
}


class ChainOfResponsibility {
 
    static func test() {
        // 组装链
        let handler1 = ConcreteHandler1()
        let handler2 = ConcreteHandler2()
        handler1.setNext(handler: handler2)

        // 可以发送请求到链上任意一个处理者
        let request = "request"
        handler1.handle(request: request)
        // handler2.handle(request: request)
    }
}
复制代码

我的使用记录:

需求

有一个场景,播放视频
点击播放按钮,不是直接把播放请求丢给播放器,需要有一系列的逻辑需要预处理,这些不同的处理步骤各自被封装成对象。通过一个称为播放拦截器的管理对象来组装。

拦截器需要做的事情有:

  1. 用户是否有权限查看(是否VIP专属视频等,无权限则提示引导等);
  2. 判断查找是否有本地资源(视频已下载,可以直接播放,不需后续步骤) ;
  3. 判断当前网络环境,是否能播放 (根据用户的设置,不符合需弹窗提醒等);
  4. 解析视频的真实播放地址

上面步骤需要按顺序依次检查,如果当前检查不符合,那么就没必要做后续检查

处理方法一:

最直接的做法,根据先后顺序写判断

if 用户无权限 {
  ...
   return
} 
if 已下载 {
    ...
    return
} 
if 网络条件不符合 {
    ...
    return
} 
if 解析不成功 {
    ...
    return
} 

拦截校验都通过,给播放器播放
复制代码

这样做能解决问题,但是如果后续有新的拦截逻辑,那么就需要继续在这里加判断代码,入侵了原来的代码。
而且上面的拦截步骤不是固定的,例如有些频道的视频是非VIP视频,那么就不需要有权限检查的步骤,有些频道的视频服务器是能直接返回真实播放地址的,那么也不需要解析地址的步骤。上面的做法不但止臃肿,而且不灵活。

处理方法二:

使用责任链模式来优化过程,也是项目中采取的方法。
通过简单工厂在拦截管理器中根据实际动态组装处理对象,而且后续如果要新增新的逻辑,不需要修改原来的代码,只要新增遵循处理者协议的对象,就可以往链上加。 而且复用性好,封装好的每个处理者对象都可以在不同的地方被复用

不写代码了,看图👀

播放请求.jpg

文章分类
iOS
文章标签