Правильная реализация наследования в Go со встроенными полями интерфейса - PullRequest
0 голосов
/ 26 августа 2018

Я играл с интерфейсами и структурами в Голанге, пытаясь реализовать «наследование», но я уверен, что сделал это неправильно. Это было бы проще объяснить на примере.

Я хочу создавать структуры из разных живых существ. Я хочу, чтобы у них был метод GetName ():

type LivingThingProvider interface {
    GetName() string
}

Теперь все они должны иметь имена и дни рождения. Для этого я создаю структуру и встраиваю в нее интерфейс:

type LivingThing struct {
    birthday string
    name     string
    LivingThingProvider
}

Я хочу добавить пару методов, которые были бы одинаковыми для всех живых существ:

func (this *LivingThing) Initialize() {
    this.birthday = time.Now().Format("02.01.2006")
}

func (this LivingThing) GetBirthday() string {
    return this.birthday
}

Теперь вот структуры, которые должны «реализовать» LivingThing:

type Frog struct {
    insectsEaten int
    LivingThing
}

func (this Frog) GetName() string {
    return fmt.Sprintf("%s-the-Frog-proud-eater-of-%d-insects", this.name, this.insectsEaten)
}

type RobotMan struct {
    LivingThing
}

func (this RobotMan) GetName() string {
    h := sha256.New()
    h.Write([]byte(this.birthday))
    return fmt.Sprintf("%s-%X", this.name, h.Sum(nil))
}

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

func main() {
    fr := Frog{}
    fr.name = "Dizzy"
    fr.insectsEaten = 586
    fr.LivingThingProvider = fr

    rm := RobotMan{}
    rm.name = "Bender"
    rm.LivingThingProvider = rm

    fr.Initialize()
    rm.Initialize()

    entities := []LivingThing{fr.LivingThing, rm.LivingThing}

    for _, ent := range entities {
        fmt.Printf("Hi, I am %s!\n", ent.GetName())
        fmt.Printf("I was born on the %s.\n", ent.GetBirthday())
    }
}

Все работает как положено, но если я удалю метод GetName () из структуры Frog или RobotMan, он будет компилироваться и паниковать после запуска:

panic: runtime error: invalid memory address or nil pointer dereference

Вот ссылка на игровую площадку: https://play.golang.org/p/h2VgvdcXJQA

У меня следующие вопросы:

1. Что я сделал "грязным" в терминах го? Если да, то как это сделать правильно?

1a. В частности, хорошо ли назначать саму структуру встроенному полю интерфейса (fr.LivingThingProvider = fr)?

2. Почему компилятор Go не проверяет, реализуют ли Frog и RobotMan интерфейс LivingThingProvider?

Заранее большое спасибо!

1 Ответ

0 голосов
/ 27 августа 2018

@ mkopriva ответь и должна получить кредит, но это похоже на какое-то объяснение, необходимое для программистов классического наследования.

Интерфейс определяет поведение, такое как Имя и День Рождения, (В Go его идиоматично, чтобы опустить Get). Структуры реализуют это поведение интерфейса. Не встраивать их.

Реализация интерфейса - это все, что вам нужно сделать, чтобы "быть" этим интерфейсом. AKA тип утки.

Когда структура T реализует String() string T, становится Стрингером.

В этом конкретном примере мы можем видеть, почему в Golang интерфейс с одним методом и встраивание интерфейса так распространены.

Поскольку у RobotMan действительно нет дня рождения, на мой взгляд, было бы лучше создать три интерфейса Namer: Name() string, Burner: Birthday() time.Time и

type LivingThingProvider interface {
    Namer
    Burner
}

Состав структуры не является заменой наследования, это другой способ повторного использования кода.

...