Go 言語プログラミング - 第 7 章 - インターフェース

Go 言語プログラミング – 第 7 章 – インターフェース

インターフェイス タイプは、他のタイプの動作を一般化および抽象化したものです。

Go 言語のインターフェイスのユニークな点は、それらが暗黙的に実装されることです。特定の型については、インターフェイスが実装する必要があるメソッドが提供されている限り、どのインターフェイスを実装するかを宣言する必要はありません。

7.1 インターフェースは規約です

7.2 インターフェースの種類

package io

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

type Closer interface {
    
    
	Close() error
}

既存のインターフェイスを結合して新しいインターフェイスを取得します。

  • 埋め込み宣言
type ReadWriter interface {
    
    
	Reader
	Writer
}
  • 直接的な発言
type ReadWriter interface {
    
    
	Read(p []byte) (n int, err error)
	Close() error
}
  • 混合ステートメント
type ReadWriter interface {
    
    
	Read(p []byte) (n int, err error)
	Writer
}

7.3 インターフェースの実装

インターフェイスの実装規則は非常に単純で、式がインターフェイスを実装する場合にのみ、その式をインターフェイスに割り当てることができます。

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 方法=

メソッドの受信側がポインター型の場合、次のようなアドレス指定されていないオブジェクトからメソッドを呼び出すことはできません。

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

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

このメソッドは IntSet 変数から呼び出すことができます

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

*IntSet のみが String メソッドを持っているため、*IntSet のみが fmt.Stringer インターフェイスを実装します。

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

空のインターフェイス タイプには任意のデータを割り当てることができます。

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

インターフェイスを実装するかどうかを決定するには、具象型のメソッドとインターフェイス型を比較す​​るだけでよいため、具象型の定義でこの関係を宣言する必要はありません。

null 以外のインターフェイス型 (io.Writer など) は、通常、特にインターフェイス型の 1 つ以上のメソッドがレシーバーの変更を意味する場合、ポインター型によって実装されます。構造体へのポインタは、最も一般的なメソッド受信者です。

7.4 flat.Value を使用したパラメータの解析

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 インターフェース値

インターフェイス型の値 (インターフェイス値と呼ばれる) には、実際には、具象型とその型の値という 2 つの部分があります。2 つは、インターフェイスの動的タイプと動的値と呼ばれます。

类型描述符名前やメソッドなど、各タイプに関する特定の情報を提供するために使用されます インターフェイス値の場合、型部分は対応する型記述子によって表現されます。

次の 4 つのステートメントでは、変数にはw3 つの異なる値があります (最初の値と最後の値は同じで、両方とも nil です)。

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

w の値と、各ステートメントの後のそれに関連する動的動作。最初のステートメントは w を宣言します。var w io.Writer
Go 言語では、変数は常に特定の値に初期化され、インターフェイスも例外ではありません。インターフェイスのゼロ値は、その動的タイプと値の両方を に設定することですnil

インターフェース値が であるかどうかはnilその動的タイプに依存するため、これは nil インターフェース値になります。w == nilまたは を使用して、w != nilインターフェイス値が であるかどうかを確認できますnil

2 番目のステートメントは、*os.File 型の値を w に割り当てます。w = os.Stdout
この割り当ては、具体的な型をインターフェイス型にプライベートに変換します。これは、io.Writer(os.Stdout)対応する明示的な変換と同等です。インターフェイスの動的タイプは、ポインター型 *os.File のタイプ記述子に設定され、その動的値は、os.Stdout のコピー (標準出力を表す os.File タイプへのポインター) に設定されます。プロセス。

Call w.Write([]byte("hello")) //
と同等os.Stdout.Write([]byte("hello"))

インターフェイス値は == 演算子と != 演算子を使用して比較できます。

2 つのインターフェイス値を比較するときに、2 つのインターフェイス値の動的タイプが一致していても、対応する動的値が比較できない場合 (スライスなど)、この比較によりダウンタイムが発生します。

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"

注: NULL ポインターを含む非 NULL インターフェース
インターフェース値が動的タイプであるかどうかは、nilその動的タイプによって決まります。

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

main が f を呼び出すと、*bytes.Buffer 型の null ポインターが out パラメーターに割り当てられるため、out の動的型は *bytes.Buffer で、動的値は nil になります。ただし、 != nil を判断することで動的タイプが決定されるため、 out != nil は true を返します。
out.Write を呼び出す場合、受信側を null にすることはできません。

正しい方法:

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

7.6 sort.Interface を使用した並べ替え

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

シーケンスを並べ替えるには、まず上記の 3 つのメソッドを実装する型を特定し、次に、sort.Sort 関数を上記のメソッドのインスタンスに適用する必要があります。

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関数

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

func ListenAndServe(address string, h Handler) error

net/http パッケージは、リクエスト マルチプレクサ ServeMux を提供します。

以下のコードでは、/list やprice などの URL が対応するハンドラーに関連付けられています。

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 は、エラー メッセージを返すメソッドを含むインターフェイス タイプです。

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 }

errors.New を直接呼び出すことはまれで、通常は fmt.Errorf が使用されます。

package jmt

import "errors"

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

7.10 アサーションの種類

型アサーションは、インターフェイス値を操作する操作であり、x.(T) のように記述されます。ここで、x はインターフェイス型の式、T はアサーション型です。型アサーションは、オペランドとしての動的型が指定されたアサーション タイプを満たすかどうかをチェックします。

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

7.11 アサーション タイプを使用したエラーの特定

os.PathError

type PathError struct {
    
    
	Op string
	Path string
	Err error
}

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

単純な文字列よりもはるかに詳細な情報が含まれる型アサーションに基づいて、特定の型に対してエラーをチェックできます。

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

7.12 インターフェイス型アサーションによるプロパティのクエリ

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 型ブランチ

型分岐は、オペランドが x.(type) に変更されることを除いて、通常の分岐ステートメントと似ています。

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

おすすめ

転載: blog.csdn.net/houzhizhen/article/details/135404448