接口
接口只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要。
当一个接口只被一个单一的具体类型实现时有一个例外,就是由于它的依赖,这个具体类型不能和这个接口存在在一个相同的包中。这种情况下,一个接口是解耦这两个包的一个好方式。
使用 %T
打印接口的动态类型
1
2
|
var w io.Writer
fmt.Printf("%T\n", w) // "<nil>"
|
一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。
一个包含nil指针的接口不是nil接口 容易难到程序员的陷阱
接口类型包含 type 和 value 两个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
const debug = true
func TestInterface(t *testing.T){
var buf *bytes.Buffer
if debug {
buf = new(bytes.Buffer)
}
f(buf)
}
f(out io.Writer){
// 函数被调用 , out 参数赋值一个指针
// 此时 out 是不为 nil 的, 但其值, 指针可能为 nil
// 所以永远为 true
if out != nil{
// 对 nil指针调用方法,panic 异常
out.Write([]byte("done!\n"))
}
}
|
sort 提供了一个排序接口
1
2
3
4
5
6
7
|
package sort
type Interface interface {
Len() int // 长度
Less(i, j int) bool // 比较,i,j 是列表索引
Swap(i, j int) // 交换
}
|
为了对序列进行排序,我们需要定义一个实现了这三个方法的类型,然后对这个类型的一个实例应用sort.Sort函数。
如
1
2
3
4
5
6
|
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
sort.Sort(StringSlice(names))
|
sort包提供了StringSlice类型,也提供了Strings函数能让上面这些调用简化成sort.Strings(names)
。
sort函数会交换很多对元素,所以如果每个元素都是指针而不是Track类型会更快,指针是一个机器字码长度而Track类型可能是八个或更多。
解析时间
1
|
time.ParseDuration("3m38s")
|
text/tabwriter包来生成一个列整齐对齐和隔开的表格
sort.Reverse函数值得进行更近一步的学习
**http.handler ** 接口
1
2
3
4
5
6
7
|
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
|
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func main() {
db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
|
路由请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/list":
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
case "/price":
// 解析 form 参数
// request.ParseForm()
// mobile := request.Form.Get("mobile")
// 解析 url 中的 query 参数
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
// 也可以使用
// msg := fmt.Sprintf("no such page: %s\n", req.URL)
// http.Error(w, msg, http.StatusNotFound) // 404
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
}
}
|
net/http包提供了一个请求多路器ServeMux来简化URL和handlers的联系。一个ServeMux将一批http.Handler聚集到一个单一的http.Handler中。
在一个项目早期使用框架是非常方便的,但是它们带来额外的复杂度会使长期的维护更加困难。
直接使用框架, 也不易于底层学习
ServeMux 请求多路器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
func main() {
db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
// 简化
// mux.HandleFunc("/list", db.list)
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
|
net/http包提供了一个全局的ServeMux实例DefaultServerMux和包级别的http.Handle和http.HandleFunc函数。
1
2
3
4
5
6
|
func main() {
db := database{"shoes": 50, "socks": 5}
http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
|
web服务器在一个新的协程中调用每一个handler,所以当handler获取其它协程或者这个handler本身的其它请求也可以访问到变量时,一定要使用预防措施,比如锁机制。
error 接口
1
2
3
4
5
6
7
|
package errors
func New(text string) error { return &errorString{text} }
type errorString struct { text string }
func (e *errorString) Error() string { return e.text }
|
**类型断言 ** x.(T)
具体类型的类型断言从它的操作对象中获得具体的值。如果检查失败,接下来这个操作会抛出panic
第二个结果表示是否断言成功
1
2
3
|
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
|
常常不会新声明一个其他名字的变量, 而是
1
2
3
4
|
// 这里并没有对 w 重新赋值, 而是创建一个局部 w 变量
if w, ok := w.(*os.File); ok {
// ...use w...
}
|
i/o 有三种经常出现的错误, os 包提供了三个帮助函数
1
2
3
|
func IsExist(err error) bool // 文件是否存在
func IsNotExist(err error) bool // 文件不存在
func IsPermission(err error) bool // 权限拒绝
|
一个更可靠的方式是使用一个专门的类型来描述结构化的错误。os包中定义了一个PathError类型来描述在文件路径操作中涉及到的失败,像Open或者Delete操作;并且定义了一个叫LinkError的变体来描述涉及到两个文件路径的操作,像Symlink和Rename。这下面是os.PathError:
1
2
3
4
5
6
7
8
9
10
11
12
|
package os
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
|
错误如下
1
2
3
4
5
|
_, err := os.Open("/no/such/file")
fmt.Println(err) // "open /no/such/file: No such file or directory"
fmt.Printf("%#v\n", err)
// Output:
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
|
通过类型断言选择行为
在函数中定义接口 , 判断传入的参数是否实现了该接口
标准库中 io.WriteString
函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// writeString writes s to w.
// If w has a WriteString method, it is invoked instead of w.Write.
func writeString(w io.Writer, s string) (n int, err error) {
type stringWriter interface {
WriteString(string) (n int, err error)
}
if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s) // avoid a copy
}
return w.Write([]byte(s)) // allocate temporary copy
}
func writeHeader(w io.Writer, contentType string) error {
if _, err := writeString(w, "Content-Type: "); err != nil {
return err
}
if _, err := writeString(w, contentType); err != nil {
return err
}
// ...
}
|
switch 还可以这样操作
1
|
switch x := x.(type) { /* ... */ }
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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))
}
}
|