上次我们提到,我们只需要实作
filterAdminTag()
filterAuthorTag()
filterRegistered()
filterCustomer()
就可以再之後透过组合的方式,达成我们想要的逻辑
当然观察一下之後,我们就会发现这段程序码重复度很高,可以做些许的调整:
fun removeTag(tags: List<Tag>, name: String): List<Tag> =
tags.filterNot { it.name == name }
fun filterAdminTag(tags: List<Tag>): List<Tag> =
removeTag(tags, "Admin")
fun filterAuthorTag(tags: List<Tag>): List<Tag> =
removeTag(tags, "Author")
fun filterRegisteredTag(tags: List<Tag>): List<Tag> =
removeTag(tags, "Registered")
fun filterCustomerTag(tags: List<Tag>): List<Tag> =
removeTag(tags, "Customer")
除了这个很好改善的问题之外,还有一个大问题,那就是我们组合过滤条件的方式
val filter: (List<Tag>) -> List<Tag> = {filterAuthorTag(filterAdminTag(it))}
只有两个条件还好,如果四个条件都要加上去
val filter: (List<Tag>) -> List<Tag> = {filterCustomerTag(filterRegisteredTag(filterAuthorTag(filterAdminTag(it))))}
这实在是太丑了!没有别的写法吗?
这边我们就要介绍到流畅介面(Fluent Interface)
以及 Kotlin 的另一个观念:扩充函数了!
什麽是流畅介面呢?
如果我们将函数的定义方式,从 函数名称(某物件) -> 某物件
的宣告方式,改成 某物件.函数名称() -> 某物件
的话,我们的逻辑就会从
函数C(函数B(函数A(某物件)))
变成
某物件.函数A()
.函数B()
.函数C()
如果逻辑合理的话,後者的设计看起来会更加简洁。
并且执行顺序从原本的由内而外,改成由上自下,更不容易搞混执行的顺序。
要让程序不需要套用 {filterCustomerTag(filterRegisteredTag(filterAuthorTag(filterAdminTag(it))))}
的结构,我们会希望过滤的条件写成类似
it
.filterAdminTag()
.filterAuthorTag()
的流程,看起来比较简洁,也不用一直去数最後的 )
有几个
要达成这个目标,我们要将 filterAdminTag()
从一个
(List<Tag>) -> List<Tag>
的函数
变成一个 List<Tag>.() -> List<Tag>
的函数
虽然我们不可能直接去改 List<Tag>
的程序码,来加入这个函数。
不过 Kotlin 允许我们直接扩充这个类别,宣告的方式也非常简洁
fun List<Tag>.filterAdminTag(): List<Tag> =
this.filterNot { it.name == "Admin" }
fun List<Tag>.filterAuthorTag(): List<Tag> =
this.filterNot { it.name == "Author" }
fun List<Tag>.filterRegisteredTag(): List<Tag> =
this.filterNot { it.name == "Registered" }
fun List<Tag>.filterCustomerTag(): List<Tag> =
this.filterNot { it.name == "Customer" }
当然,我们的 updateUsersTags()
也要做对应的调整
filter
的型态变成 List<Tag>.()->List<Tag>
预设值变成了 {this}
,代表这个 lambda 直接回传原本的 List<Tag>
最後,呼叫方式从 filter(tags)
改成 tags.filter()
fun updateUsersTags(users: List<User>, tags: List<Tag>, filter: List<Tag>.()->List<Tag> = {this}) {
transaction {
users.forEach {
it.tags = SizedCollection(tags.filter())
}
}}
接着我们来看看测试怎麽调整。
我们的 测试更新标签时如过滤Admin,结果应不出现Admin
可以变成
@Test
fun `测试更新标签时如过滤Admin,结果应不出现Admin`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
val admin = Tag.new { name = "Admin" }
updateUsersTags(listOf(testUser), listOf(testTag, admin)) {
this.filterAdminTag()
}
assertThat(testUser.tags.toList(), not(hasItem(admin)))
}
}
测试更新标签时如过滤Admin和Author,结果应不出现Admin和Author
则变成了
@Test
fun `测试更新标签时如过滤Admin和Author,结果应不出现Admin和Author`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
val admin = Tag.new { name = "Admin" }
val author = Tag.new { name = "Author" }
updateUsersTags(
listOf(testUser),
listOf(testTag, admin, author)
) {
this
.filterAdminTag()
.filterAuthorTag()
}
assertThat(testUser.tags.toList(), not(hasItem(admin)))
assertThat(testUser.tags.toList(), not(hasItem(author)))
}
}
运作测试後,确认原本的逻辑都成功通过,我们就可以休息一下了!
休息一下过後,我们看到刚刚被修改後,没有用到的 filterTag()
fun removeTag(tags: List<Tag>, name: String): List<Tag> =
tags.filterNot { it.name == name }
fun List<Tag>.filterAdminTag(): List<Tag> =
this.filterNot { it.name == "Admin" }
fun List<Tag>.filterAuthorTag(): List<Tag> =
this.filterNot { it.name == "Author" }
fun List<Tag>.filterRegisteredTag(): List<Tag> =
this.filterNot { it.name == "Registered" }
fun List<Tag>.filterCustomerTag(): List<Tag> =
this.filterNot { it.name == "Customer" }
突然发现:不对呀!我们还可以这样改
fun List<Tag>.removeTag(name: String): List<Tag> =
this.filterNot { it.name == name }
这样程序就可以写成类似
this
.removeTag("Admin")
.removeTag("Author")
不仅未来弹性变高,而且程序码还更少了!
我们来改看看 测试更新标签时如过滤Admin,结果应不出现Admin
@Test
fun `测试更新标签时如过滤Admin,结果应不出现Admin`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
val admin = Tag.new { name = "Admin" }
updateUsersTags(listOf(testUser), listOf(testTag, admin)) {
this.removeTag("Admin")
}
assertThat(testUser.tags.toList(), not(hasItem(admin)))
}
}
测试更新标签时如过滤Admin和Author,结果应不出现Admin和Author
再改成
@Test
fun `测试更新标签时如过滤Admin和Author,结果应不出现Admin和Author`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
val admin = Tag.new { name = "Admin" }
val author = Tag.new { name = "Author" }
updateUsersTags(
listOf(testUser),
listOf(testTag, admin, author)
) {
this
.removeTag("Admin")
.removeTag("Author")
}
assertThat(testUser.tags.toList(), not(hasItem(admin)))
assertThat(testUser.tags.toList(), not(hasItem(author)))
}
}
执行之後,确认我们的测试都会通过,今天的重构就大功告成罗!
<<: 用React刻自己的投资Dashboard Day10 - 用useCallback hook帮你记住函式
在过去的5-7年当中,ML已经不再只限於研究人员能够接触、使用,越来越多的AI/ML工具以及产品出现...
CH9: 时间管理 「专业开发人员同样清楚会议的高昂成本,他们同样清楚自己的时间是宝贵的。所以,如果...
前言 在开始进入我们各式各样的深度模型之前,我们要先来介绍一个非常 Powerful 的 Deep ...
我的文章系列快要变成每天30分钟,学一点新的东西了。 一样是继续跟着教程走,Implement da...
With tight_layout() import numpy as np import mat...