четверг, 25 июля 2019 г.

Объектно-ориентированное программирование с наследованием в Golang

Наследование в традиционных объектно-ориентированных языках предлагает три функции в одном. Когда Cat наследуется от Animal:

  • класс Cat повторно использует код из класса Animal,
  • переменная x типа Animal может ссылаться либо к Cat, либо к Animal,
  • x.Eat() выберет метод Eat в зависимости от того, к какому типу относится объект x.

В объектно-ориентированном жаргоне эти функции известны как повторное использование кода (code reuse), полиморфизм и динамическая диспетчеризация (dynamic dispatch).

Все они доступны в Go, используя отдельные конструкции:

  • композиция (composition) и встраивание (embedding) обеспечивают повторное использование кода,
  • интерфейсы заботятся о полиморфизме и динамической диспетчеризации.

Повторное использование кода посредством композиции (composition)

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

Если Cat требуются некоторые или все функциональные возможности Animal, просто используйте композицию (composition).

type Animal struct {
    // …
}

type Cat struct {
    fluffy Animal
    // …
}

Это дает вам полную свободу использования Animal части вашей Cat по мере необходимости.

Повторное использование кода посредством встраивания (embedding)

Если класс Cat наследует точное поведение Animal, такой подход может привести к утомительному написанию повторяющегося кода.

type Animal struct {
    // …
}

func (a *Animal) Eat()   { … }
func (a *Animal) Sleep() { … }
func (a *Animal) Walk() { … }

type Cat struct {
    fluffy Animal
    // …
}

func (a *Cat) Eat()   { a.fluffy.Eat() }
func (a *Cat) Sleep() { a.fluffy.Sleep() }
func (a *Cat) Walk()  { a.fluffy.Walk() }

Этот шаблон написания кода известен как делегирование.

Go использует встраивание для подобных ситуаций. Объявление структуры Cat и ее трех методов можно сократить до:

type Cat struct {
    Animal
    // …
}

Полиморфизм и динамическая диспетчеризация с интерфейсами

Держите свои интерфейсы короткими и вводите их только при необходимости.

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

Если вам нужно усыпить всех своих питомцев, вы можете определить интерфейс Sleeper.

type Sleeper interface {
    Sleep()
}

func main() {
    pets := []Sleeper{new(Cat), new(Dog)}
    for _, x := range pets {
        x.Sleep()
    }
}

Никакого явного объявления не требуется для типов Cat и Dog. Любой тип, который предоставляет методы, названные в интерфейсе, может рассматриваться как реализация.


Читайте также:


Комментариев нет:

Отправить комментарий