接口
- 接口关注于类型可以做什么,而不是存储了什么。
- 接口通过列举类型必须满足的一组方法来进行声明。
- 在 Go 语言中,不需要显式声明接口。
- 任何类型的任何值,只要它满足了接口的要求,就能够成为变量t的值
package main
import (
"fmt"
"strings"
)
type martian struct{}
func (m martian) talk() string {
return "nack nack"
}
type laser int
func (l laser) talk() string {
return strings.Repeat("pew ", int(l))
}
func main() {
var t interface {
talk() string
}
t = martian{}
fmt.Println(t.talk())
t = laser(3)
fmt.Println(t.talk())
}
接口类型
- 为了复用,通常会把接口声明为类型。
- 按约定,接口名称通常以 er 结尾。
package main
import (
"fmt"
"strings"
)
type martian struct{}
func (m martian) talk() string {
return "nack nack"
}
type laser int
func (l laser) talk() string {
return strings.Repeat("pew ", int(l))
}
type talker interface {
talk() string
}
func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}
func main() {
shout(martian{})
shout(laser(2))
type crater struct{}
// crater does not implement talker (missing talk method)
// shout(crater{})
}
- 接口可以与 struct 嵌入 特性一同使用。
package main
import (
"fmt"
"strings"
)
type laser int
func (l laser) talk() string {
return strings.Repeat("pew ", int(l))
}
type talker interface {
talk() string
}
func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}
func main() {
type starship struct {
laser
}
s := starship{laser(3)}
fmt.Println(s.talk())
shout(s)
}
- 同时使用组合和接口将构成非常强大的设计工具。
探索接口
- Go 语言的接口都是隐式满足的。
package main
import (
"fmt"
"time"
)
type stardater interface {
YearDay() int
Hour() int
}
// stardate returns a fictional measure of time.
func stardate(t stardater) float64 {
doy := float64(t.YearDay())
h := float64(t.Hour()) / 24.0
return 1000 + doy + h
}
type sol int
func (s sol) YearDay() int {
return int(s % 668)
}
func (s sol) Hour() int {
return 0
}
func main() {
day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf("%.1f Curiosity has landed\n", stardate(day))
s := sol(1422)
fmt.Printf("%.1f Happy birthday\n", stardate(s))
}
满足接口
- Go 标准库导出了很多只有单个方法的接口。
- Go 通过简单的、通常只有单个方法的接口……来鼓励组合而不是继承,这些接口在各个组件之间形成了简明易懂的界限。
—— Rob Pike - 例如 fmt 包声明的 Stringer 接口:
package main
import "fmt"
// location with a latitude, longitude in decimal degrees.
type location struct {
lat, long float64
}
// String formats a location with latitude, longitude.
func (l location) String() string {
return fmt.Sprintf("%v, %v", l.lat, l.long)
}
func main() {
curiosity := location{-4.5895, 137.4417}
fmt.Println(curiosity)
}
- 标准库中常用接口还包括:io.Reader,io.Writer,json.Marshaler…
作业题
- 基于刚才小测试的答案,扩展并编写一个用 JSON 格式输出坐标的程序。这个程序的 JSON 输出应该分别用十进制(DD)和度/分/秒(DMS)两种格式提供坐标:
- 通过满足定制 JSON 数据的 json.Marshaler 接口,你应该无需修改坐标结构就能够达到上述目的。在编写 MarshalJSON 方法的时候可以考虑使用 json.Marshal 函数。
- 可以使用 22 章介绍过的 decimal 方法
package main
import (
"encoding/json"
"fmt"
"os"
)
// coordinate in degrees, minutes, seconds in a N/S/E/W hemisphere.
type coordinate struct {
d, m, s float64
h rune
}
// String formats a DMS coordinate.
func (c coordinate) String() string {
return fmt.Sprintf("%vº%v'%.1f\" %c", c.d, c.m, c.s, c.h)
}
// decimal converts a d/m/s coordinate to decimal degrees.
func (c coordinate) decimal() float64 {
sign := 1.0
switch c.h {
case 'S', 'W', 's', 'w':
sign = -1
}
return sign * (c.d + c.m/60 + c.s/3600)
}
func (c coordinate) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
DD float64 `json:"decimal"`
DMS string `json:"dms"`
D float64 `json:"degrees"`
M float64 `json:"minutes"`
S float64 `json:"seconds"`
H string `json:"hemisphere"`
}{
DD: c.decimal(),
DMS: c.String(),
D: c.d,
M: c.m,
S: c.s,
H: string(c.h),
})
}
// location with a latitude, longitude in decimal degrees.
type location struct {
Name string `json:"name"`
Lat coordinate `json:"latitude"`
Long coordinate `json:"longitude"`
}
func main() {
elysium := location{
Name: "Elysium Planitia",
Lat: coordinate{4, 30, 0.0, 'N'},
Long: coordinate{135, 54, 0, 'E'},
}
bytes, err := json.MarshalIndent(elysium, "", " ")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(bytes))
}