Как два разных типа реализуют один и тот же метод в golang, используя интерфейсы?

Скажем, у меня две структуры:

type First struct {
    str string
}
type Second struct {
    str string
}

И я хочу, чтобы оба они реализовали интерфейс A:

type A interface {
    PrintStr() //print First.str or Second.str
}

Кажется излишним иметь реализацию как для первой, так и для второй структур следующим образом:

func (f First) PrintStr() {
    fmt.Print(f.str)
}

func (s Second) PrintStr() {
    fmt.Print(s.str)
}

Есть ли способ, которым я могу иметь одну реализацию для всех структур, реализующих интерфейс A? Что-то вроде этого, но, похоже, это не работает:

func (a A) PrintStr() {
    fmt.Print(a.str)
}

Спасибо!

Ответ 1

Нет, вы не можете, , но, вы можете создать базовый тип, а затем вставить его в свою 2-структуру, поэтому нужна только реализация для базового типа:

type WithString struct {
    str string
}

type First struct {
    WithString
}

type Second struct {
    WithString
}

type A interface {
    PrintStr() //print First.str or Second.str
}

func (w WithString) PrintStr() {
    fmt.Print(w.str)
}

Использование:

a := First{
    WithString: WithString{
        str: "foo",
    },
}

Полный пример игровой площадки

Вставить документацию

Ответ 2

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

В вашем случае метод PrintStr используется для печати строки, которая является членом каждой структуры.
В этом случае это означает, что каждая структура должна реализовать интерфейс, который возвращает нужную строку, используемую для печати, а PrintStr становится функцией, принимающей параметр Printable.

type First struct {
    str string
}
type Second struct {
    str string
}

type Printable interface {
     String() string
}

func (p First) String() string {
    return p.str
}

func (p Second) String() string {
    return p.str
}

func PrintStr(p Printable) {
    fmt.Print(p.String())
}

Использование интерфейса A не является идиоматическим, поскольку интерфейс не должен зависеть от реализации его функциональности.

Вместо этого с этим решением вы все равно можете сохранить интерфейс A, но упростите каждую реализацию:

func (f First) PrintStr() {
    PrintStr(f)
}

func (s Second) PrintStr() {
    PrintStr(s)
}

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

Этот шаблон распространен в стандартной библиотеке Go, потому что многие полезные функции построены на интерфейсах, которые они не могут распространяться, например io.Reader.
Это простой интерфейс только с одним методом, но он используется полностью из многих других пакетов.
Если вы посмотрите на ioutil.ReadAll, можно утверждать, что он мог быть реализован как другой метод интерфейса io.Reader, однако это упрощает чтение читателей, концентрируясь на их единственном методе, позволяя любому разработчику использовать ReadAll бесплатно.

Ответ 3

Возможно, это не лучший способ решить вашу проблему, но вы можете использовать оболочку, чтобы избежать "реализации" функции дважды, примерно так:

type First struct {
    str StringWrapper
}
type Second struct {
    str StringWrapper
}


type StringWrapper struct {
    str string
}
func (f StringWrapper) PrintStr() {
    fmt.Print(f.str)
}

func main() {
    var a First = First{str:StringWrapper{str: "aaa"}};
    a.str.PrintStr();
}

Ответ 4

Почему бы просто не оставить эту функцию вне интерфейса и передать тип A в качестве параметра?

type A interface {}

type First struct {
    str string
}
type Second struct {
    str string
}

func PrintStr(a A) {
    fmt.Print(a.str)
}