golang的fmt包String(),Error(),Format(),GoString()的介面實現

何裕發發表於2019-01-12

簡介

golang的介面使用非常廣泛,幾乎每一個包都會用到介面,fmt包的使用率最多之一。在實際開發中,要定義結構體的標準輸出用String(),定義標準錯誤輸出Error(),定義格式化輸出Format(),還有比較特殊的GoString()。接下來描述介面的使用方式,使用場景,還有注意的地方。

String()

type TestString struct {}
func (t TestString) String() string {
	return "我是String"
}
func main() {
    fmt.Println(TestString{})
}
複製程式碼
我是String
複製程式碼

使用起來比較簡單,只要結構體裡面有String() string就可以輸出。
fmt包裡面會判斷有沒有fmt.Stringer的介面,然後再呼叫。
通常用於結構體的預設輸出,例如:

type Student struct {
	number int
 	realname string
	age int
}
func main() {
	stu := &Student{
		number: 1,
		realname: "王小明",
		age: 18,
	}
	fmt.Println(stu)
}
複製程式碼
&{1 王小明 18}
複製程式碼

改成:

type Student struct {
	number int
 	realname string
	age int
}
func (t *Student) String() string {
	return fmt.Sprintf("學號: %d\n真實姓名: %s\n年齡: %d\n", t.number, t.realname, t.age)
}
func main() {
	stu := &Student{
		number: 1,
		realname: "王小明",
		age: 18,
	}
	fmt.Println(stu)
}
複製程式碼
學號: 1
真實姓名: 王小明
年齡: 18
複製程式碼

瞬間感覺高大上了吧!!

Error


type TestError struct {}
func (t TestError) Error() string {
	return "我是Error"
}
func main() {
    fmt.Println(TestString{})
}
複製程式碼
我是Error
複製程式碼

實際上使用方式跟String()一樣,但是設計程式碼時不能互相替換實現。
最常用的用法是獨立封裝type XXXError struct{},在文章最尾會揣摸一下為什麼要這樣用。

Format

type TestFormat struct {}
func (t TestFormat) Format(s fmt.State, c rune) {
	switch c {
	case 'c':
		switch {
		case s.Flag('+'):
			fmt.Printf("我是+c\n")
		default:
			fmt.Fprint(s, "我是c\n")
		}
	default:
		fmt.Print("我是Format")
	}
}
func main() {
    t := TestFormat{}
    fmt.Println(t)
    fmt.Printf("%c\n", t)
    fmt.Printf("%+c\n", t)
    fmt.Printf("%s\n", t)
}
複製程式碼
我是Format
我是c
我是+c
我是Format
複製程式碼

fmt.Println也會呼叫Format的介面,所以String() Format()不能同一個結構體裡面。 通常使用跟Error()類似,可以參考一下github.com/pkg/errors裡的stack.gofunc (f Frame) Format(s fmt.State, verb rune)

GoString

type TestGoString struct {}
func (t TestGoString) GoString() string {
	return "我是GoString"
}
func main() {
    t := TestGoString{}
    fmt.Println(TestGoString{})
    fmt.Printf("%s %#v\n", t, t)
}
複製程式碼
{}
{} 我是GoString
複製程式碼

如上所示fmt.Println並沒呼叫GoString方法,只能通過格式化%#+標記輸出。
在沒有實現介面的情況下,通常用來輸出預設相應值,如下:

func main() {
	var i uint = 18
	// 輸出十六進位制
	fmt.Printf("%x\n", i)
	fmt.Printf("%#x\n", i)
}
複製程式碼
12
0x12
複製程式碼

注意事項

fmt/print.gopp.handleMethods(verb rune) (handled bool)

func (p *pp) handleMethods(verb rune) (handled bool) {
	...
	// 判斷Formatter
	if formatter, ok := p.arg.(Formatter); ok {
	    ...
	    formatter.Format(p, verb)
	    return
	}
    
        // 判斷是否含有#識別符號
	if p.fmt.sharpV {
	        // 判斷GoStriner
		if stringer, ok := p.arg.(GoStringer); ok {
                        ...
			p.fmt.fmtS(stringer.GoString())
			return
		}
	} else {
		switch verb {
		case 'v', 's', 'x', 'X', 'q':
			switch v := p.arg.(type) {
			// 符合error介面
			case error:
                                ...
				p.fmtString(v.Error(), verb)
				return
                        // 符合Stringer介面
			case Stringer:
                                ...
				p.fmtString(v.String(), verb)
				return
			}
		}
	}
	return false
}

複製程式碼

Format -> (#)GoString -> ((v,s,x,X,q)Error -> String) 原始碼四個介面都在handlerMethods方法呼叫控制,都不是互相獨立,根據優先順序呼叫。所以介面的設計,儘可能獨立封裝,避免混淆。

小結

String() 用於對結構體的標準輸出等。
Error() 封裝error的方法,可以改一些錯誤上傳到日誌系統或者列印Stack。
Format() 對於String()的高階用法,用於多種型別或者格式使用。
GoString() 常用於相對值。


歡迎大神們交流,指導!

相關文章