我们好不容易写了 userAddTag()
和 updateUsersTags()
的逻辑,突然又出现了新需求!
这次需求单位希望 updateUsersTags()
後面可以加上过滤的功能,在套用时可以避免某些 tag
被错误的套上。
比方说,在一般更新时,我们不会将 admin
标签套用在一般使用者上,这应该是只能套用在程序管理员身上的标签才对。
并且需求单位希望能根据情况,过滤掉以下一个或多个标签:
admin
author
registered
customer
那麽,我们能不能调整看看 updateUsersTags()
来满足这个需求呢?
一开始我们可能会想:这有什麽难的?再写四个函数
updateUsersTagsWithoutAdmin
updateUsersTagsWithoutAuthor
updateUsersTagsWithoutRegistered
updateUsersTagsWithoutCustomer
不就好了吗?
不过我们仔细一看,发现到需求内的一个小细节:「一个或多个标签」
这下就不能这样做了,不然岂不是变成了
updateUsersTagsWithoutAdminAndAuthor
updateUsersTagsWithoutAdminAndAuthorAndRegistered
这时我们就要换个思考方式,想想怎麽在一个函数内,满足所有的过滤条件了!
这时候,我们就要善用函数式编程的概念,使用函数的思考领域来架构逻辑!
{:height="300px" width="400px"}
所谓函数式编程,简单的说,就是将函数视为一个个体,可以当作参数传到其他函数里面去,也可以变成回传值
首先我们想到,如果我们将过滤条件本身,设计成一个一个的函数。那麽我们原先的 updateUsersTags()
就不用修改太多,只需要在後面加入一个 过滤条件
的函数,并套用到要加入的 tags
上,不就可以了吗?
我们来试着调整一下 updateUsersTags()
fun updateUsersTags(users: List<User>, tags: List<Tag>, filter: (List<Tag>) -> List<Tag>) {
transaction {
users.forEach {
it.tags = SizedCollection(filter(tags))
}
}
}
这样改了之後,尝试执行一下测试。我们就会发现一件很糟糕的事情:缺少 filter
参数的话,原先的测试都无法通过了!
我们可以加上一个预设值,如果我们没有设定任何 filter
,那麽就不会滤除任何 tag
,全部都会通过
fun updateUsersTags(users: List<User>, tags: List<Tag>, filter: (List<Tag>) -> List<Tag> = { it }) {
transaction {
users.forEach {
it.tags = SizedCollection(filter(tags))
}
}
}
运作一下测试,确认全部都会通过
然後我们加上过滤掉 Admin
标签的函数 filterAdminTag()
fun filterAdminTag(tags: List<Tag>): List<Tag> =
tags.filterNot { it.name == "Admin" }
再加上过滤掉 Author
标签的函数 filterAuthorTag()
fun filterAdminTag(tags: List<Tag>): List<Tag> =
tags.filterNot { it.name == "Admin" }
接着我们就可以测试罗!
加上测试案例 测试更新标签时如过滤Admin,结果应不出现Admin
这边我们利用 ::
符号,直接呼叫 filterAdminTag()
函数
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), ::filterAdminTag)
assertThat(testUser.tags.toList(), not(hasItem(admin)))
}
}
运作一下测试,我们就会发现这段程序确实通过了,testUser.tags
内确实不包含 admin
!
细心的读者可能会发现:那如果我们要同时过滤 Admin
和 Author
标签的话,该怎麽办呢?
别担心!我们已经提供了足够的材料给之後的工程师,之後维护的工程师一定可以组合出他所需要的逻辑。
之後的工程师只要这样写
val filter: (List<Tag>) -> List<Tag> = {filterAuthorTag(filterAdminTag(it))}
就可以将两个函数组合成另一个新的函数
我们来尝试看看测试案例 测试更新标签时如过滤Admin和Author,结果应不出现Admin和Author
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" }
val filter: (List<Tag>) -> List<Tag> = {filterAuthorTag(filterAdminTag(it))}
updateUsersTags(
listOf(testUser),
listOf(testTag, admin, author), filter)
assertThat(testUser.tags.toList(), not(hasItem(admin)))
assertThat(testUser.tags.toList(), not(hasItem(author)))
}
}
测试之後,我们就可以发现案例通过了!
<<: 我们的基因体时代-AI, Data和生物资讯 Day24- 使用tidyverse观念来分析基因资料:plyranges
>>: Swift纯Code之旅 Day14. 「TableView(5) - 点击TableViewCell」
今天就来看一下人脸辨识 + 物件追踪可以迸出什麽样的火花吧! 本文开始 在applications目...
近年来 ROS (Robot Operating System,机器人作业系统) 目前已成熟应用於智...
案例说明及适用场景 签核与流程及人员组织架构为正相关 一般简易的判断包含金额,商品类型,申请人或由申...
昨天PO完文重看一下旧文才发现前天说要讲json做轮播,结果昨天先讲了tab…. 希望今天能讲完轮播...
如果我有一个小猫类别,我想要这个小猫类别有飞行功能,你会怎麽做? 直接写一个有飞行功能的小鸟类别,然...