web-dev-qa-db-ja.com

Golangでインターフェースが必要なのはなぜですか?

Golangでは、レシーバーメソッドで構造体を使用します。ここまではすべて完璧です。
しかし、インターフェイスが何なのかわかりません。構造体にメソッドを定義し、構造体にメソッドを実装する場合は、とにかく別の構造体の下にメソッドを記述します。
これは、インターフェースが単なるメソッド定義のように見えることを意味し、ページ上の余分なスペースを余分に使用します。

インターフェイスが必要な理由を説明する例はありますか?

28
nikoss

インターフェースはここでは詳細な答えを出すにはトピックが大きすぎますが、その使用方法を明確にするためのいくつかのことがあります。

インターフェースはtoolです。それらを使用するかどうかはあなた次第ですが、コードを明確にすることができ、パッケージ間、またはクライアント(ユーザー)とサーバー(プロバイダー)の間でNice APIを提供できます。

はい、独自のstructタイプを作成でき、それにメソッドを「アタッチ」できます。次に例を示します。

_type Cat struct{}

func (c Cat) Say() string { return "meow" }

type Dog struct{}

func (d Dog) Say() string { return "woof" }

func main() {
    c := Cat{}
    fmt.Println("Cat says:", c.Say())
    d := Dog{}
    fmt.Println("Dog says:", d.Say())
}
_

上記のコードにはすでにいくつかの繰り返しがあります:CatDogの両方に何かを言うとき。 animalのように、両方を同じ種類のエンティティとして処理できますか?あんまり。確かに両方を_interface{}_として処理できますが、そうする場合、_interface{}_型の値はメソッドを定義しないため、Say()メソッドを呼び出すことはできません。

上記の両方のタイプにいくつかのの類似性があります:両方とも同じシグネチャ(パラメーターと結果タイプ)を持つメソッドSay()を持っています。インターフェイスでこれをcaptureできます:

_type Sayer interface {
    Say() string
}
_

インターフェイスには、メソッドのsignaturesのみが含まれ、メソッドのimplementationは含まれません。

Goでは、メソッドセットがインターフェースのスーパーセットである場合、タイプが暗黙的にインターフェースを実装することに注意してください。意図の宣言はありません。これは何を意味するのでしょうか?以前のCatおよびDog型は、このSayerインターフェイスを既に実装していますが、このインターフェイスの定義は、以前に記述したときにも存在していなかったため、それらまたは何かをマークします。彼らはただやる。

インターフェイスは動作を指定します。インターフェースを実装する型は、その型がインターフェースが「規定する」すべてのメソッドを持っていることを意味します。

両方ともSayerを実装しているため、両方をSayerの値として扱うことができ、共通しています。両方を統一して処理する方法をご覧ください。

_animals := []Sayer{c, d}
for _, a := range animals {
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}
_

(これは、タイプ名を取得することだけを反映しているので、今のところあまり使わないでください。)

重要な部分は、CatDogの両方を同じ種類(インターフェイスタイプ)として処理し、それらを操作/使用できることです。 Say()メソッドを使用して追加の型をすばやく作成する場合は、CatおよびDogの横に並べることができます。

_type Horse struct{}

func (h Horse) Say() string { return "neigh" }

animals = append(animals, Horse{})
for _, a := range animals {
    fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}
_

これらの型で動作する他のコードを書きたいとしましょう。ヘルパー関数:

_func MakeCatTalk(c Cat) {
    fmt.Println("Cat says:", c.Say())
}
_

はい、上記の関数はCatでのみ機能します。似たようなものが必要な場合は、タイプごとに記述する必要があります。これがどれほど悪いかは言うまでもない。

はい、_interface{}_の引数を取るように記述し、 type assertion または type Switches を使用できます。これにより、ヘルパー関数の数が減りますが、本当にいです。

ソリューション?はい、インターフェース。関数を宣言して、それを使用したい動作を定義するインターフェイスタイプの値を取得します。それがすべてです。

_func MakeTalk(s Sayer) {
    fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say())
}
_

この関数は、CatDogHorse、またはSay()メソッドを持つ '今まで知らない他のタイプの値で呼び出すことができます。涼しい。

Go Playground でこれらの例を試してください。

64
icza

インターフェイスはいくつかの種類のジェネリックを提供します。アヒルのタイピングについて考えます。

type Reader interface{
     Read()
}

func callRead(r Reader){
      r.Read()
}

type A struct{
}
func(_ A)Read(){
}

type B struct{
}
func(_ B)Read(){
}

構造体ABcallReadに渡すことは問題ありません。どちらもReaderインターフェースを実装しているからです。ただし、インターフェイスがない場合は、ABの2つの関数を記述する必要があります。

func callRead(a A){
     a.Read()
}

func callRead2(b B){
     b.Read()
}
5
zzn

ここでは、Goのインターフェイスの2つの興味深い使用例を示します。

1-次の2つのシンプルなインターフェースをご覧ください。

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

type Writer interface {
    Write(p []byte) (n int, err error)
}

これらの2つのシンプルなインターフェイスを使用して、この興味深い魔法を実行できます。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    file, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    w := io.MultiWriter(file, os.Stdout)
    r := strings.NewReader("You'll see this string twice!!\n")
    io.Copy(w, r)

    slice := []byte{33, 34, 35, 36, 37, 38, 39, 10, 13}
    io.Copy(w, bytes.NewReader(slice)) // !"#$%&'

    buf := &bytes.Buffer{}
    io.Copy(buf, bytes.NewReader(slice))
    fmt.Println(buf.Bytes()) // [33 34 35 36 37 38 39 10 13]

    _, err = file.Seek(0, 0)
    if err != nil {
        panic(err)
    }

    r = strings.NewReader("Hello\nWorld\nThis\nis\nVery\nnice\nInterfacing.\n")
    rdr := io.MultiReader(r, file)
    scanner := bufio.NewScanner(rdr)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

出力:

You'll see this string twice!!
!"#$%&'

[33 34 35 36 37 38 39 10 13]
Hello
World
This
is
Very
Nice
Interfacing.
You'll see this string twice!!
!"#$%&'

このコードが十分に明確であることを願っています:
strings.NewReaderを使用して文字列から読み取り、io.Copy(w, r)だけでos.Stdoutを使用してfileio.MultiWriterの両方に同時に書き込みます。次に、bytes.NewReader(slice)を使用してスライスから読み取り、fileos.Stdoutの両方に同時に書き込みます。次に、スライスをバッファio.Copy(buf, bytes.NewReader(slice))にコピーし、file.Seek(0, 0)を使用してOriginファイルに移動し、strings.NewReaderを使用して文字列から最初に読み取り、io.MultiReader(r, file)を使用してfile ]およびbufio.NewScannerおよびfmt.Println(scanner.Text())を使用してすべてを印刷します。


2-そして、これはインターフェイスの別の興味深い使用法です:

package main

import "fmt"

func main() {
    i := show()
    fmt.Println(i) // 0

    i = show(1, 2, "AB", 'c', 'd', []int{1, 2, 3}, [...]int{1, 2})
    fmt.Println(i) // 7

}
func show(a ...interface{}) (count int) {
    for _, b := range a {
        if v, ok := b.(int); ok {
            fmt.Println("int: ", v)
        }
    }
    return len(a)
}

出力:

0
int:  1
int:  2
7

素敵な例: Goのタイプアサーションの説明

次も参照してください: Go:インターフェイスの意味は何ですか{}?

3
user6169399
  1. 構造体に関係なくメソッドを実装する必要がある場合。

    ローカルの構造体にアクセスし、構造体を知る前にハンドラーを使用するハンドラーメソッドがある場合があります。

  2. 他の構造体または現在の構造体に固有の動作が必要な場合。

    ユーザーがインターフェースを使用しない可能性があるため、いくつかの方法でインターフェースを表示したい場合があります。ユースケースごとに構造体を分割したい場合があります。

  3. 何かを実装する型が必要な場合。

    あなたはタイプを知っているかもしれませんが、少なくとも値は持っています。

1
You sir