Почему нужны интерфейсы в Голанге?

В Голанге мы используем структуры с методами приемника. здесь все идеально. Однако я не уверен, какие интерфейсы. Мы определяем методы в структурах, и если мы хотим реализовать метод в структуре, мы все равно записываем его в другую структуру. Это означает, что интерфейсы, по-видимому, являются просто определениями методов, за исключением просто лишнего пространства на нашей странице.

Есть ли какой-нибудь пример, объясняющий, зачем мне нужен интерфейс?

Ответ 1

Интерфейсы - слишком большая тема, чтобы дать здесь исчерпывающий ответ, но некоторые вещи, которые делают их использование понятным.

Интерфейсы - это инструмент. Независимо от того, используете вы их или нет, это зависит от вас, но они могут сделать код более понятным, коротким, более читаемым, и они могут обеспечить хороший 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())
}

Мы уже можем видеть некоторое повторение в приведенном выше коде: когда заставляем Cat и Dog что-то сказать. Можем ли мы обращаться с ними как с одним и тем же видом сущности, как с животным? На самом деле, нет. Конечно, мы можем обрабатывать оба как interface{}, но если мы это сделаем, мы не сможем вызвать их метод Say(), потому что значение типа interface{} не определяет никаких методов.

В обоих вышеупомянутых типах есть некоторое сходство: оба имеют метод Say() с одинаковой сигнатурой (параметры и типы результатов). Мы можем сделать это с помощью интерфейса:

type Sayer interface {
    Say() string
}

Интерфейс содержит только сигнатуры методов, но не их реализацию.

Обратите внимание, что в Go тип неявно реализует интерфейс, если его набор методов является надмножеством интерфейса. Там нет декларации о намерениях. Что это значит? Наши предыдущие типы Cat и Dog уже реализовали этот интерфейс Sayer, даже несмотря на то, что это определение интерфейса даже не существовало, когда мы их писали ранее, и мы не трогали их, чтобы пометить их или что-то в этом роде. Они просто делают.

Интерфейсы определяют поведение. Тип, который реализует интерфейс, означает, что у типа есть все методы, которые интерфейс "предписывает".

Поскольку оба реализуют Sayer, мы можем обрабатывать оба как значение Sayer, у них есть это общее. Посмотрите, как мы можем справиться как в единстве:

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

(That reflect part is only to get the type name, don't make much of it as of now.)

Важной частью является то, что мы можем обрабатывать как Cat, так и Dog как один и тот же тип (тип интерфейса), и работать с ними/использовать их. Если вы быстро приступили к созданию дополнительных типов с помощью метода 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{} и использовать утверждение типа или переключатели типа, что уменьшит количество вспомогательных функций, но все равно будет выглядеть ужасно.

Решение? Да, интерфейсы. Просто объявите функцию, чтобы получить значение типа интерфейса, который определяет поведение, которое вы хотите с ним делать, и все:

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

Вы можете вызывать эту функцию со значением Cat, Dog, Horse или любого другого типа, не известного до сих пор, который имеет метод Say(). Круто.

Попробуйте эти примеры на Go Playground.

Ответ 2

Интерфейс

предоставляет некоторые виды дженериков. Подумайте о том, как печатать утки.

type Reader interface{
     Read()
}

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

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

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

Хорошо передать struct A и B в callRead, потому что оба реализуют интерфейс Reader. Но если без интерфейса, мы должны написать две функции для A и B.

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

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

Ответ 3

Здесь я покажу два интересных примера использования интерфейсов в Go:

1- См. эти два простых интерфейса:

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

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

Используя эти два простых интерфейса, вы можете сделать эту интересную магию:

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 и записывает одновременно как file, так и os.Stdout с помощью io.MultiWriter только с io.Copy(w, r). Затем читается из среза с помощью bytes.NewReader(slice) и одновременно записывается как file, так и os.Stdout. Затем скопируйте срез в буфер io.Copy(buf, bytes.NewReader(slice)), затем перейдите в начало файла, используя file.Seek(0, 0), затем сначала прочитайте строку, используя strings.NewReader, затем продолжите чтение этого file с помощью io.MultiReader(r, 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: В чем смысл интерфейса {}?

Ответ 4

  1. если вам нужен метод \s для реализации независимо от структуры.

    у вас может быть метод обработчика для доступа к вашим локальным структурам и использования обработчика до того, как вы узнаете структуру.

  2. если вам нужно поведение, уникальное для другой или текущей структуры.

    вы можете захотеть, чтобы ваш интерфейс просматривался несколькими способами, потому что пользователи никогда не смогут их использовать. Вы могли бы хотеть, чтобы ваши структуры были разделены на варианты использования.

  3. если вам нужен тип, который реализует что-либо.

    вы можете знать или нет тип, но по крайней мере у вас есть значение.