欢迎重视微信大众号:FSA全栈举动

  • Kotlin – 改进工厂形式
  • Kotlin – 改进构建者形式
  • Kotlin – 改进观察者形式
  • Kotlin – 改进策略形式
  • Kotlin – 改进迭代器形式
  • Kotlin – 改进职责链形式
  • Kotlin – 改进装修者形式

一、前语

  • 职责链形式
    • 效果:避免恳求的发送者和接纳者之间的耦合关系,将这个目标连成一条链,并沿着这条链传递该恳求,直到有一个目标处理它为止。
    • 举例:OKHttp 的拦截器、Servlet 中的 FilterChain

二、运用职责链形式

  • 比如:学生会经费请求
  • 要点:1 个恳求会在 n 个处理器组成的处理器链上传递

以学生会经费请求会例,学生会会有一些日常开支以及活动开支,需求向学院的学生会基金请求经费,假如金额在 100 元之内,由分部长批阅;假如金额在 100 到 500 元之间,由会长批阅;假如金额在 500 到 1000 元之间,由学院辅导员批阅;而假如金额超过 1000 元,则默许打回请求。像这种需求一层层往后传递恳求的状况,十分适合选用职责链形式来规划程序:

/**
 * 经费请求事情
 *
 * @author GitLqr
 */
data class ApplyEvent(val money: Int, val title: String)
/**
 * 经费批阅处理器
 *
 * @author GitLqr
 */
interface ApplyHandler {
    val successor: ApplyHandler?
    fun handleEvent(event: ApplyEvent)
}

留意:职责链形式需求将处理器目标连成一条链,最简单粗暴的办法便是让前驱处理器持有后继处理器 successor

接着,依据事例需求,编写各个人物对应的处理器类:

/**
 * 部长
 *
 * @author GitLqr
 */
class GroupLeader(override val successor: ApplyHandler?) : ApplyHandler {
    override fun handleEvent(event: ApplyEvent) {
        when {
            event.money <= 100 -> println("Group Leader handled application: ${event.title}.")
            successor != null -> successor.handleEvent(event)
            else -> println("Group Leader: This application cannot be handled.")
        }
    }
}
/**
 * 会长
 *
 * @author GitLqr
 */
class President(override val successor: ApplyHandler?) : ApplyHandler {
    override fun handleEvent(event: ApplyEvent) {
        when {
            event.money <= 500 -> println("President handled application: ${event.title}.")
            successor != null -> successor.handleEvent(event)
            else -> println("President: This application cannot be handled.")
        }
    }
}
/**
 * 学院
 *
 * @author GitLqr
 */
class College(override val successor: ApplyHandler?) : ApplyHandler {
    override fun handleEvent(event: ApplyEvent) {
        when {
            event.money <= 1000 -> println("College handled application: ${event.title}.")
            successor != null -> successor.handleEvent(event)
            else -> println("College: This application cannot be handled.")
        }
    }
}

最终,创立各个人物处理器实例,并按顺序组成一条链,由链头开端接纳、转发需求被处理的经费请求事情:

// 运用
// val college = College(null)
// val president = President(college)
// val groupLeader = GroupLeader(president)
val groupLeader = GroupLeader(President(College(null)))
groupLeader.handleEvent(ApplyEvent(10, "buy a pen")) // 买只钢笔
groupLeader.handleEvent(ApplyEvent(200, "team building")) // 团建
groupLeader.handleEvent(ApplyEvent(600, "hold a debate match")) // 举办辩论赛
groupLeader.handleEvent(ApplyEvent(1200, "annual meeting of the college")) // 学院年会
// 输出
Group Leader handled application: buy a pen.
President handled application: team building.
College handled application: hold a debate match.
College: This application cannot be handled.

从输出成果能够看到,经费请求事情会在处理器链上传递,直到被一个合适的处理器处理并停止。

留意:这话是针对当前事例说的,职责链形式没有硬性要求一个恳求只能被一个处理器处理,你能够在前面的处理器中对恳求进行加工,提取数据等等操作,而且能够挑选是否放行,交由后边的处理器持续处理,这需求依据实际状况,灵活应变。

三、改进职责链形式

  • 比如:学生会经费请求
  • 要点:偏函数 Partial Function

在对上述事例进行改进之前,咱们先来了解一下偏函数是什么,在不同的编程语言中,对偏函数的了解还不相同,在 Python 中,偏函数是运用 functools.partial 把一个函数的某些参数给固定住(也便是设置默许值),回来一个新的函数,调用这个新函数会更简单。而在 Scala 中,偏函数是运用 PartialFunction 构建一个仅仅处理输入参数的部分分支的函数,换句话说,便是带有判别条件的函数,只有满足条件的参数,才会被函数处理。

以上结论来自以下两篇文章:

  • 廖雪峰官方网站上的 Python 《偏函数》www.liaoxuefeng.com/wiki/101695…
  • 数据工匠记(作者:匠人李)的 Scala 《偏函数(Partial Function)》www.lllpan.top/article/77

题外话:对 Scala 偏函数有爱好的能够看一下上面的文章,写的很通透。

回过头来,职责链形式的中心机理是,整个链条上的每个处理环节都有对其输入的校验标准,当输入的参数处于某个职责链节的有用接纳规模之内,该环节才能对其做出正常的处理操作。那么,咱们是不是能够把链条上的每个处理环节看做是一个个的偏函数呢?是的,不过 Kotlin 中并没有内置偏函数 API,好在有一个第三方 Kotlin 函数库【funKTionale】,其间的 partialfunctions.kt 就有 Scala 中偏函数的类似完成:

// https://github.com/MarioAriasC/funKTionale/blob/master/funktionale-utils/src/main/kotlin/org/funktionale/utils/partialfunctions.kt
class PartialFunction<in P1, out R>(private val definetAt: (P1) -> Boolean, private val f: (P1) -> R) : (P1) -> R {
    override fun invoke(p1: P1): R {
        if (definetAt(p1)) {
            return f(p1)
        } else {
            throw IllegalArgumentException("Value: ($p1) isn't supported by this function")
        }
    }
    fun isDefinedAt(p1: P1) = definetAt(p1)
}

这个 PartialFunction 类第一眼看上去感觉好复杂,分红如下几步,便利了解:

  • PartialFunction 继承自一个函数类型 (P1) -> R,编译器会强制要求完成 invoke() 办法,这意味着 PartialFunction 实例目标能够像调用函数那样运用。
  • 结构参数 1 definetAt: (P1) -> Boolean 用于判别 P1 参数是否满足被处理的条件。
  • 结构参数 2 f: (P1) -> R 用于处理 P1 参数并回来 R 类型值。
  • 成员办法 invoke 中,当 P1 满足条件时,则将 P1 交给 结构参数 2 f: (P1) -> R 处理;不然抛出反常。
  • 成员办法 isDefinedAt 仅仅结构参数 1 definetAt 的复制。

所以,用一句话概括 PartialFunction 实例目标,便是一个带有判别条件的”函数”,只有满足条件的参数,才会被”函数”处理。现在咱们用一个个 PartialFunction 实例来代替处理器是彻底没问题的,问题是怎样把它们链接起来呢?【funKTionale】中还为 PartialFunction 扩展了一个 orElse 函数,这便是把偏函数组合起来的要害:

// https://github.com/MarioAriasC/funKTionale/blob/master/funktionale-utils/src/main/kotlin/org/funktionale/utils/partialfunctions.kt
infix fun <P1, R> PartialFunction<P1, R>.orElse(that: PartialFunction<P1, R>): PartialFunction<P1, R> {
    return PartialFunction({ this.isDefinedAt(it) || that.isDefinedAt(it) }) {
        when {
            this.isDefinedAt(it) -> this(it)
            that.isDefinedAt(it) -> that(it)
            else -> throw IllegalArgumentException("function not definet for parameter ($it)")
        }
    }
}

相同,也分红如下几步,便利了解:

  • orElsePartialFunction 的扩展函数,故内部能够运用 this 获取本来的 PartialFunction 实例(也便是 receiver)。
  • orElse 只接纳一个 PartialFunction 类型参数 that,而且回来一个 PartialFunction 类型实例,故 orElse 能够嵌套调用。
  • orElse 回来值是一个运用了两个 PartialFunction 实例目标 (即 thisthat)组合出来的一个新的 PartialFunction 实例目标,
  • orElse 回来值的目的是,只要本来的 thisthat 中有一个条件成立,那么就让条件成立的那个来处理参数 P1 ,不然抛出反常。其实,这个 that 就适当于是职责链形式中的 successor
  • orElse 运用 infix 润饰,故支持中缀表达式写法。

留意:你或许一时看不懂 PartialFunction({ xxx }){ yyy } 这个奇怪的语法,其实很简单,在创立一个 PartialFunction 实例时,能够传入两个 Lambda 表达式,所以正常写法应该是这样的 PartialFunction({ xxx }, { yyy }) ,不过,在 Kotlin 中,当 Lambda 表达式作为最终一个参数传入时,能够写到函数外部,所以就出现了 PartialFunction({ xxx }){ yyy } 这种写法。

好了,现在用 PartialFunction 来改进本来的职责链形式代码:

/**
 * 运用自运行Lambda来构建一个个 PartialFunction 实例:部长、会长、学院
 *
 * @author GitLqr
 */
val groupLeader = {
    val definetAt: (ApplyEvent) -> Boolean = { it.money <= 200 }
    val handler: (ApplyEvent) -> Unit = { println("Group Leader handled application: ${it.title}.") }
    PartialFunction(definetAt, handler)
}()
val president = {
    val definetAt: (ApplyEvent) -> Boolean = { it.money <= 500 }
    val handler: (ApplyEvent) -> Unit = { println("President handled application: ${it.title}.") }
    PartialFunction(definetAt, handler)
}()
val college = {
    val definetAt: (ApplyEvent) -> Boolean = { true }
    val handler: (ApplyEvent) -> Unit = {
        when {
            it.money <= 1000 -> println("College handled application: ${it.title}.")
            else -> println("College: This application is refused.")
        }
    }
    PartialFunction(definetAt, handler)
}()

留意:自运行 Lambda 适当于是 js 中的当即执行函数。

接下来便是用 orElse 将一个个 PartialFunction 实例链接起来:

// 运用
// val applyChain = groupLeader.orElse(president.orElse(college))
val applyChain = groupLeader orElse president orElse college // 中缀表达式
applyChain(ApplyEvent(10, "buy a pen")) // 买只钢笔
applyChain(ApplyEvent(200, "team building")) // 团建
applyChain(ApplyEvent(600, "hold a debate match")) // 举办辩论赛
applyChain(ApplyEvent(1200, "annual meeting of the college")) // 学院年会
// 输出
Group Leader handled application: buy a pen.
Group Leader handled application: team building.
College handled application: hold a debate match.
College: This application is refused.

运用 PartialFunction 之后,不只能够不幅度减少代码量,结合 orElse 能取得更好的语法表达。以上,便是运用偏函数改进职责链形式的全部内容了。为了加深对偏函数的了解,这里引用数据工匠记的 Scala 《偏函数(Partial Function)》原文中的话:

为什么要用偏函数呢?以我个人愚见,还是一个重用粒度的问题。函数式的编程思维是以一种“演绎法”而非“概括法”去寻求处理空间。也便是说,它并不是要去概括问题然后分解问题并处理问题,而是看透问题本质,定义最原初的操作和组合规则,面临问题时,能够通过组合各种函数去处理问题,这也正是“组合子(combinator)”的意义。偏函数则更进一步,将函数求解空间中各个分支也分离出来,形成能够被组合的偏函数。

假如文章对您有所帮助, 请不惜点击重视一下我的微信大众号:FSA全栈举动, 这将是对我最大的激励. 大众号不只有Android技能, 还有iOS, Python等文章, 或许有你想要了解的技能知识点哦~

Kotlin - 改良责任链模式