iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0

今天,我們一起來看

body {
	h1 {
		+"Hello from $name!"
	}
}

這段程式的實作。

我們先來看看 body

inline fun HTML.body(classes : String? = null, crossinline block : BODY.() -> Unit = {}) : Unit = BODY(attributesMapOf("class", classes), consumer).visit(block)

BODY 的實作則是

open class BODY(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("body", consumer, initialAttributes, null, false, false), HtmlBlockTag

head 不同,這邊實作的不是 HtmlHeadTag,而是 HtmlBlockTag

interface HtmlBlockTag : CommonAttributeGroupFacade, FlowContent {
}

這邊我們可以看到,雖然 headbody 很像

但是他們並不是實作一個定義非常多函數的介面

而是實作一個介面,該介面再去實作其他介面。

這就討論到我們在設計物件導向程式時所會提到的一個原則:介面隔離原則

與其設計一個功能很多的介面,然後實作該介面的物件需要用其他方式去繞過不需要實作的函數

其實我們可以設計許多個小介面,然後讓物件去實作他們。

如果這樣會導致物件總是需要實作很多介面,那我們就像現在這樣

設計一個介面來實作對應的介面們。

這邊的 BODY 還定義了一個 attributesMapOf("class", classes)

我們來看看其實作

fun attributesMapOf(key: String, value: String?): Map<String, String> = when (value) {
    null -> emptyMap
    else -> singletonMapOf(key, value)
}
fun singletonMapOf(key: String, value: String): Map<String, String> = SingletonStringMap(key, value)
private data class SingletonStringMap(override val key: String, override val value: String) : Map<String, String>,
    Map.Entry<String, String> {
    override val entries: Set<Map.Entry<String, String>>
        get() = setOf(this)

    override val keys: Set<String>
        get() = setOf(key)

    override val size: Int
        get() = 1

    override val values: Collection<String>
        get() = listOf(value)

    override fun containsKey(key: String) = key == this.key
    override fun containsValue(value: String) = value == this.value
    override fun get(key: String): String? = if (key == this.key) value else null
    override fun isEmpty() = false
}

這邊最終定義出了一個 private data class 來協助我們管理 body 所會包含的 class 參數。

接著我們往下看 h1

/**
 * Heading
 */
@HtmlTagMarker
inline fun FlowOrHeadingContent.h1(classes : String? = null, crossinline block : H1.() -> Unit = {}) : Unit = H1(attributesMapOf("class", classes), consumer).visit(block)
@Suppress("unused")
open class H1(initialAttributes : Map<String, String>, override val consumer : TagConsumer<*>) : HTMLTag("h1", consumer, initialAttributes, null, false, false), CommonAttributeGroupFacadeFlowHeadingPhrasingContent {

}

H1 除了和 HEAD 一樣繼承了 HTMLTag,不同的地方是還實作了 CommonAttributeGroupFacadeFlowHeadingPhrasingContent 介面

interface CommonAttributeGroupFacadeFlowHeadingPhrasingContent : CommonAttributeGroupFacade, CommonAttributeGroupFacadeFlowHeadingContent, FlowPhrasingContent, HtmlBlockInlineTag, HtmlBlockTag, HtmlInlineTag {
}

我們逐一看過這些介面,會發現一個有趣的地方

interface HtmlBlockTag : CommonAttributeGroupFacade, FlowContent {
}

interface HtmlHeadTag : CommonAttributeGroupFacade, MetaDataContent {
}

interface HtmlInlineTag : CommonAttributeGroupFacade, PhrasingContent {
}

這些介面都繼承了 CommonAttributeGroupFacade

interface CommonAttributeGroupFacade : Tag {  
}

我們再往下看 Tag

@HtmlTagMarker
interface Tag {
    val tagName: String
    val consumer: TagConsumer<*>
    val namespace: String?

    val attributes: MutableMap<String, String>
    val attributesEntries: Collection<Map.Entry<String, String>>

    val inlineTag: Boolean
    val emptyTag: Boolean

    operator fun Entities.unaryPlus(): Unit {
        entity(this)
    }

    operator fun String.unaryPlus(): Unit {
        text(this)
    }

    fun text(s: String) {
        consumer.onTagContent(s)
    }

    fun text(n: Number) {
        text(n.toString())
    }

    fun entity(e: Entities) {
        consumer.onTagContentEntity(e)
    }

    fun comment(s: String) {
        consumer.onTagComment(s)
    }
}

到這邊我們就可以看到 Tag 設計的方式了,除了定義了 HTML 標籤常會使用的參數之外,也預設定義了幾個要生成標籤會需要的函數。

基本上的生成細節,如我們之前看過的,還是以 consumer 實作為主

到這邊我們就看過了

body {
	h1 {
		+"Hello from $name!"
	}
}

這兩個函數的實作,對於 Ktor 怎麼生成 HTML 內容,有了更加清楚的認識。


上一篇
Day 10:從 head title 等函數窺探神通廣大的 HTMLTag 物件
下一篇
Day 12:處理 API 輸入輸出格式的 ContentNegotiation
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言