上次我們提到,我們只需要實作
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)))
}
}
執行之後,確認我們的測試都會通過,今天的重構就大功告成囉!