Go Language Programming-Chapter 7--Interface

Go Language Programming – Chapter 7 – Interfaces

Interface types are generalizations and abstractions of the behavior of other types.

The unique thing about interfaces in Go language is that they are implemented implicitly. For a specific type, there is no need to declare which interfaces it implements, as long as the methods that the interface must implement are provided.

7.1 Interface is a convention

7.2 Interface type

package io

type Reader interface {
    
    
	Read(p []byte) (n int, err error)
}

type Closer interface {
    
    
	Close() error
}

Combine existing interfaces to get new interfaces.

  • embedded declaration
type ReadWriter interface {
    
    
	Reader
	Writer
}
  • Direct statement
type ReadWriter interface {
    
    
	Read(p []byte) (n int, err error)
	Close() error
}
  • mixed statement
type ReadWriter interface {
    
    
	Read(p []byte) (n int, err error)
	Writer
}

7.3 Implement interface

The implementation rules of interfaces are very simple. Only when an expression implements an interface, this expression can be assigned to the interface.

var w io.Writer
w = os.Stdout // OK: *os.File 有 Writable 方法
w = new(bytes.Buffer) // OK: * bytes.Buffer 有 Writable 方法
w = time.Second // 编译错误: time.Duration 没有 Writable 方法=

If the receiver of the method is a pointer type, the method cannot be called from an unaddressed object, such as:

type IntSet struct {
    
    }
func (*IntSet) String() string

var _ = IntSet{
    
    }.String() // 编译错误:String 方法需要 *IntSet 接收者

This method can be called from an IntSet variable

var s IntSet
var _ = s.String() // OK: s 是一个变量,&s 有 String 方法。

Because only *IntSet has the String method, only *IntSet implements the fmt.Stringer interface.

var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // 编译错误: IntSet 缺少 String 方法。

Any data can be assigned to an empty interface type.

var any interface{
    
    } // 空接口类型
any = true
any = 12.34
any = "hello"
any = map[string]int {
    
    "one":1}
fmt.Println(any)

Determining whether to implement an interface only requires comparing the methods of the concrete type and the interface type, so there is no need to declare this relationship in the definition of the concrete type.

Non-null interface types (such as io.Writer) are usually implemented by a pointer type, especially when one or more methods of the interface type imply modification of the receiver. A pointer to a structure is the most common method recipient.

7.4 Using flat.Value to parse parameters

tempflag.go

package main

import (
	"flag"
	"fmt"
)

type Celsius float64
type Fahrenheit float64

func CToF(c Celsius) Fahrenheit {
    
     return Fahrenheit(c*9.0/5.0 + 32.0) }
func FToC(f Fahrenheit) Celsius {
    
     return Celsius((f - 32.0) * 5.0 / 9.0) }

func (c Celsius) String() string {
    
     return fmt.Sprintf("%g°C", c) }

/*
//!+flagvalue
package flag

// Value is the interface to the value stored in a flag.
type Value interface {
	String() string
	Set(string) error
}
//!-flagvalue
*/

//!+celsiusFlag
// *celsiusFlag satisfies the flag.Value interface.
type celsiusFlag struct{
    
     Celsius }

func (f *celsiusFlag) Set(s string) error {
    
    
	var unit string
	var value float64
	fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed
	switch unit {
    
    
	case "C", "°C":
		f.Celsius = Celsius(value)
		return nil
	case "F", "°F":
		f.Celsius = FToC(Fahrenheit(value))
		return nil
	}
	return fmt.Errorf("invalid temperature %q", s)
}

//!-celsiusFlag

//!+CelsiusFlag

// CelsiusFlag defines a Celsius flag with the specified name,
// default value, and usage, and returns the address of the flag variable.
// The flag argument must have a quantity and a unit, e.g., "100C".
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
    
    
	f := celsiusFlag{
    
    value}
	flag.CommandLine.Var(&f, name, usage)
	return &f.Celsius
}
var temp = CelsiusFlag("temp", 20.0, "the temperature")
func main() {
    
    
	flag.Parse()

	fmt.Println(*temp)
}

7.5 Interface values

A value of an interface type (referred to as an interface value) actually has two parts: a concrete type and a value of that type. The two are called the dynamic type and dynamic value of the interface.

Used to类型描述符 provide specific information about each type, such as its name and methods. For an interface value, the type part is expressed by the corresponding type descriptor.

In the following four statements, the variable whas three different values ​​(the initial and last values ​​are the same, both are nil)

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

The value of w and the associated dynamic behavior after each statement. The first statement declares w: var w io.Writer
In the Go language, variables are always initialized to a specific value, and interfaces are no exception. The zero value for an interface is to set both its dynamic type and value to nil.

Whether an interface value is nildepends on its dynamic type, so now this is a nil interface value. You can use w == nilor w != nilto check whether an interface value is nil.

The second statement assigns a value of type *os.File to w: w = os.Stdout
this assignment privately converts a concrete type to an interface type, which is io.Writer(os.Stdout)equivalent to the corresponding explicit conversion. The dynamic type of the interface will be set to the type descriptor of the pointer type *os.File, and its dynamic value will be set to a copy of os.Stdout, a pointer to the os.File type representing the standard output of the process.

Call w.Write([]byte("hello")) //
Equivalent toos.Stdout.Write([]byte("hello"))

Interface values ​​can be compared using the == and != operators.

When comparing two interface values, if the dynamic types of the two interface values ​​are consistent but the corresponding dynamic values ​​are incomparable (such as slice), then this comparison will cause downtime.

var w io.Writer
fmt.Printf("%T\n", w) // "<nil>"

w = os.Stdout
fmt.Printf("%T\n", w) // "*os.File"

w = new(bytes.Buffer)
fmt.Printf("%T\n", w) // "*bytes.Buffer"

NOTE: Non-null interfaces containing null pointers
Whether an interface value is nildepends on its dynamic type.

const debug = true

func f(out io.Writer) {
    
    
	if out != nil {
    
    
		out.Write([]byte("done!\n"))
	}
}
func main() {
    
    
	var buf *bytes.Buffer
	if debug {
    
    
		buf = new(bytes.Buffer) // 启用输出收集
	}
	f(buf)
}

When main calls f, a null pointer of type *bytes.Buffer is assigned to the out parameter, so the dynamic type of out is *bytes.Buffer and the dynamic value is nil. But judging out != nil determines its dynamic type, so out != nil returns true.
When calling out.Write, the receiver is not allowed to be null.

Correct method:

var buf io.Writer
if debug {
    
    
	buf = new(bytes.Buffer)
}

7.6 Sorting using sort.Interface

package sort
type Interface interface {
    
    
	Len() int
	Less(i, j int) bool // i, j 是序列元素的下标
	Swap(i, j int)
}

To sort a sequence, you need to first identify a type that implements the above three methods, and then apply the sort.Sort function to instances of the above methods.

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))

7.7 http.Handler function

package http
type Handler interface {
    
    
	ServeHTTP(w ResponseWriter, r *Request)
}

func ListenAndServe(address string, h Handler) error

The net/http package provides a request multiplexer ServeMux.

In the code below, URLs such as /list and price are associated with the corresponding handlers.

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
    
    
	db := database{
    
    "shoes": 50, "socks": 5}
	mux := http.NewServeMux()
	//!+main
	mux.HandleFunc("/list", db.list)
	mux.HandleFunc("/price", db.price)
	//!-main
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

type database map[string]int

func (db database) list(w http.ResponseWriter, req *http.Request) {
    
    
	for item, price := range db {
    
    
		fmt.Fprintf(w, "%s: $%d\n", item, price)
	}
}

func (db database) price(w http.ResponseWriter, req *http.Request) {
    
    
	item := req.URL.Query().Get("item")
	if price, ok := db[item]; ok {
    
    
		fmt.Fprintf(w, "$%d\n", price)
	} else {
    
    
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "no such item: %q\n", item)
	}
}

7.8 error interface

Error is an interface type that contains methods that return error messages

type error interface {
    
    
	Error() string
}

func New(text string) error {
    
     return &errorString{
    
    text}}

type errorString struct {
    
    text string}

func (e *errorString) Error() string {
    
     return e.Text }

It is rare to call errors.New directly. Generally, fmt.Errorf is used.

package jmt

import "errors"

func Errorf(format string, args ...interface{
    
    }) error {
    
    
	return errors.New(Sprintf(format, args...))
}

7.10 Assertion types

A type assertion is an operation that operates on an interface value, written like x.(T), where x is an expression of the interface type and T is an assertion type. Type assertions check whether the dynamic type as an operand satisfies the specified assertion type.

if f, ok := w.(*os.File); ok {
    
    
	// 使用 f
}

7.11 Using assertion types to identify errors

os.PathError

type PathError struct {
    
    
	Op string
	Path string
	Err error
}

func (e *PathError) Error() string {
    
    
	return e.Op + " " + ": " + e.Err.Error()
}

Errors can be checked against specific types based on type assertions, which contain far more detail than a simple string.

_, err := os.Open("/no/such/file")
fmt.Println(err)
fmt.Printf("%#v\n", err)

7.12 Querying properties through interface type assertions

func writeString(w io.Writer, s string)(n int, err error) {
    
    
	type stringWriter interface {
    
    
		WritesString(string) (n int, err error)
	}

	if sw, ok := w.(StringWriter); ok {
    
    
		return sw.WriteString(s) // 避免了内存复制
	}
	return w.Write([]byte(s))
}

7.13 Type branches

Type branches are similar to ordinary branch statements, except that the operand is changed to x.(type).

switch x.(type) {
    
    
	case nil:  //
	case init, unit: //
	case bool: //
}

Guess you like

Origin blog.csdn.net/houzhizhen/article/details/135404448