Go 語(yǔ)言 類(lèi)型分支

2023-03-14 16:55 更新

原文鏈接:https://gopl-zh.github.io/ch7/ch7-13.html


7.13. 類(lèi)型分支

接口被以兩種不同的方式使用。在第一個(gè)方式中,以io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler和error為典型,一個(gè)接口的方法表達(dá)了實(shí)現(xiàn)這個(gè)接口的具體類(lèi)型間的相似性,但是隱藏了代碼的細(xì)節(jié)和這些具體類(lèi)型本身的操作。重點(diǎn)在于方法上,而不是具體的類(lèi)型上。

第二個(gè)方式是利用一個(gè)接口值可以持有各種具體類(lèi)型值的能力,將這個(gè)接口認(rèn)為是這些類(lèi)型的聯(lián)合。類(lèi)型斷言用來(lái)動(dòng)態(tài)地區(qū)別這些類(lèi)型,使得對(duì)每一種情況都不一樣。在這個(gè)方式中,重點(diǎn)在于具體的類(lèi)型滿足這個(gè)接口,而不在于接口的方法(如果它確實(shí)有一些的話),并且沒(méi)有任何的信息隱藏。我們將以這種方式使用的接口描述為discriminated unions(可辨識(shí)聯(lián)合)。

如果你熟悉面向?qū)ο缶幊?,你可能?huì)將這兩種方式當(dāng)作是subtype polymorphism(子類(lèi)型多態(tài))和 ad hoc polymorphism(非參數(shù)多態(tài)),但是你不需要去記住這些術(shù)語(yǔ)。對(duì)于本章剩下的部分,我們將會(huì)呈現(xiàn)一些第二種方式的例子。

和其它那些語(yǔ)言一樣,Go語(yǔ)言查詢一個(gè)SQL數(shù)據(jù)庫(kù)的API會(huì)干凈地將查詢中固定的部分和變化的部分分開(kāi)。一個(gè)調(diào)用的例子可能看起來(lái)像這樣:

import "database/sql"

func listTracks(db sql.DB, artist string, minYear, maxYear int) {
    result, err := db.Exec(
        "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
        artist, minYear, maxYear)
    // ...
}

Exec方法使用SQL字面量替換在查詢字符串中的每個(gè)'?';SQL字面量表示相應(yīng)參數(shù)的值,它有可能是一個(gè)布爾值,一個(gè)數(shù)字,一個(gè)字符串,或者nil空值。用這種方式構(gòu)造查詢可以幫助避免SQL注入攻擊;這種攻擊就是對(duì)手可以通過(guò)利用輸入內(nèi)容中不正確的引號(hào)來(lái)控制查詢語(yǔ)句。在Exec函數(shù)內(nèi)部,我們可能會(huì)找到像下面這樣的一個(gè)函數(shù),它會(huì)將每一個(gè)參數(shù)值轉(zhuǎn)換成它的SQL字面量符號(hào)。

func sqlQuote(x interface{}) string {
    if x == nil {
        return "NULL"
    } else if _, ok := x.(int); ok {
        return fmt.Sprintf("%d", x)
    } else if _, ok := x.(uint); ok {
        return fmt.Sprintf("%d", x)
    } else if b, ok := x.(bool); ok {
        if b {
            return "TRUE"
        }
        return "FALSE"
    } else if s, ok := x.(string); ok {
        return sqlQuoteString(s) // (not shown)
    } else {
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
    }
}

switch語(yǔ)句可以簡(jiǎn)化if-else鏈,如果這個(gè)if-else鏈對(duì)一連串值做相等測(cè)試。一個(gè)相似的type switch(類(lèi)型分支)可以簡(jiǎn)化類(lèi)型斷言的if-else鏈。

在最簡(jiǎn)單的形式中,一個(gè)類(lèi)型分支像普通的switch語(yǔ)句一樣,它的運(yùn)算對(duì)象是x.(type)——它使用了關(guān)鍵詞字面量type——并且每個(gè)case有一到多個(gè)類(lèi)型。一個(gè)類(lèi)型分支基于這個(gè)接口值的動(dòng)態(tài)類(lèi)型使一個(gè)多路分支有效。這個(gè)nil的case和if x == nil匹配,并且這個(gè)default的case和如果其它c(diǎn)ase都不匹配的情況匹配。一個(gè)對(duì)sqlQuote的類(lèi)型分支可能會(huì)有這些case:

switch x.(type) {
case nil:       // ...
case int, uint: // ...
case bool:      // ...
case string:    // ...
default:        // ...
}

和(§1.8)中的普通switch語(yǔ)句一樣,每一個(gè)case會(huì)被順序的進(jìn)行考慮,并且當(dāng)一個(gè)匹配找到時(shí),這個(gè)case中的內(nèi)容會(huì)被執(zhí)行。當(dāng)一個(gè)或多個(gè)case類(lèi)型是接口時(shí),case的順序就會(huì)變得很重要,因?yàn)榭赡軙?huì)有兩個(gè)case同時(shí)匹配的情況。default case相對(duì)其它c(diǎn)ase的位置是無(wú)所謂的。它不會(huì)允許落空發(fā)生。

注意到在原來(lái)的函數(shù)中,對(duì)于bool和string情況的邏輯需要通過(guò)類(lèi)型斷言訪問(wèn)提取的值。因?yàn)檫@個(gè)做法很典型,類(lèi)型分支語(yǔ)句有一個(gè)擴(kuò)展的形式,它可以將提取的值綁定到一個(gè)在每個(gè)case范圍內(nèi)都有效的新變量。

switch x := x.(type) { /* ... */ }

這里我們已經(jīng)將新的變量也命名為x;和類(lèi)型斷言一樣,重用變量名是很常見(jiàn)的。和一個(gè)switch語(yǔ)句相似地,一個(gè)類(lèi)型分支隱式的創(chuàng)建了一個(gè)詞法塊,因此新變量x的定義不會(huì)和外面塊中的x變量沖突。每一個(gè)case也會(huì)隱式的創(chuàng)建一個(gè)單獨(dú)的詞法塊。

使用類(lèi)型分支的擴(kuò)展形式來(lái)重寫(xiě)sqlQuote函數(shù)會(huì)讓這個(gè)函數(shù)更加的清晰:

func sqlQuote(x interface{}) string {
    switch x := x.(type) {
    case nil:
        return "NULL"
    case int, uint:
        return fmt.Sprintf("%d", x) // x has type interface{} here.
    case bool:
        if x {
            return "TRUE"
        }
        return "FALSE"
    case string:
        return sqlQuoteString(x) // (not shown)
    default:
        panic(fmt.Sprintf("unexpected type %T: %v", x, x))
    }
}

在這個(gè)版本的函數(shù)中,在每個(gè)單一類(lèi)型的case內(nèi)部,變量x和這個(gè)case的類(lèi)型相同。例如,變量x在bool的case中是bool類(lèi)型和string的case中是string類(lèi)型。在所有其它的情況中,變量x是switch運(yùn)算對(duì)象的類(lèi)型(接口);在這個(gè)例子中運(yùn)算對(duì)象是一個(gè)interface{}。當(dāng)多個(gè)case需要相同的操作時(shí),比如int和uint的情況,類(lèi)型分支可以很容易的合并這些情況。

盡管sqlQuote接受一個(gè)任意類(lèi)型的參數(shù),但是這個(gè)函數(shù)只會(huì)在它的參數(shù)匹配類(lèi)型分支中的一個(gè)case時(shí)運(yùn)行到結(jié)束;其它情況的它會(huì)panic出“unexpected type”消息。雖然x的類(lèi)型是interface{},但是我們把它認(rèn)為是一個(gè)int,uint,bool,string,和nil值的discriminated union(可識(shí)別聯(lián)合)



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)