Как использовать интерфейс, где один из методов возвращает объекты того же интерфейса? - PullRequest
0 голосов
/ 27 апреля 2020

Я все еще нахожусь вокруг интерфейсов в Go, но я пишу пакет, который возьмет график структур и выведет их в stdout в виде дерева. Для этого я определил interface, называемый TreeNode, который содержит метод GetChildren, который должен возвращать часть структур, реализующих TreeNode. Таким образом, я могу откинуть дерево, начиная с root. то есть вот полное определение интерфейса:

type TreeNode interface {
    GetName() string
    GetChildren() []TreeNode
}

Я слышал, что это лучший способ "принимать интерфейсы и возвращать структуры". Что имеет большой смысл. Тем не менее, я начинаю сталкиваться с проблемами, когда пытаюсь реализовать эти методы на struct. Вот полный пример:

package main

import "fmt"

type MyStruct struct {
    Name     string
    Children []*MyStruct
}

type TreeNode interface {
    GetName() string
    GetChildren() []TreeNode
}

func PrintTree(root TreeNode) {
    // Print the nodes recursively
    fmt.Println(root.GetName())
    // etc.
}

func main() {
    child1 := &MyStruct{
        Name: "Child 1",
    }
    child2 := &MyStruct{
        Name: "Child 2",
    }
    root := &MyStruct{
        Name:     "Root",
        Children: []*MyStruct{child1, child2},
    }
    PrintTree(root)
}

func (my_struct *MyStruct) GetName() string {
    return my_struct.Name
}

func (my_struct *MyStruct) GetChildren() []*MyStruct {
    return my_struct.Children
}

При вызове PrintTree в main компилятор жалуется, что

cannot use root (type *MyStruct) as type TreeNode in argument to PrintTree:
    *MyStruct does not implement TreeNode (wrong type for GetChildren method)
        have GetChildren() []*MyStruct
        want GetChildren() []TreeNode

Это немного удивительно, так как интерфейс говорит, что GetChildren() должен возвращать объекты, которые реализуют TreeNode, а *MyStruct делает реализацию TreeNode. Но ясно, что мне чего-то не хватает в том, как компилятор справляется с такой ситуацией (где тип интерфейса необходим рекурсивно).

Если я приму совет компилятора (и совет , это похоже вопрос ) и измените реализацию MyStruct на GetChildren, чтобы вернуть []TreeNode В реализации GetChildren() появляется новая ошибка компилятора, говорящая

cannot use my_struct.Children (type []*MyStruct) as type []TreeNode in return argument

, что также является неожиданностью. Плюс, похоже, все согласны с тем, что я должен возвращать struct s из методов вместо того, чтобы возвращать interface s. Я определенно мог бы жить с возвратом []TreeNode из этого метода, но я, должно быть, что-то упустил, потому что компилятор не в восторге.

Go Версия: go version go1.13.8 darwin/amd64

1 Ответ

3 голосов
/ 27 апреля 2020

«Принимать интерфейсы, возвращать структуры» - это не правило, а практика, облегчающая жизнь. Однако в этом случае с вашим определением интерфейса у вас не так много вариантов.

Вы можете сохранить свой дизайн, но реализовать GetChildren:

func (my_struct *MyStruct) GetChildren() []TreeNode {
    ret:=make([]TreeNode,0,len(my_struct.Children))
    for _,x:=range my_struct.Children {
        ret=append(ret,x)
    }
    return ret
}

Это необходимо из-за строгий характер системы типов Go.

Кроме того, вы можете немного изменить свой дизайн:

type MyStruct struct {
    Name     string
    Children []TreeNode
}

Это потребует от вас использования утверждений типа, если вам нужен доступ основные структуры при работе с Children. Однако это позволит вам строить деревья с узлами разных типов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...