Две минуты позволят вам понять, как наследование в Go

Go
Две минуты позволят вам понять, как наследование в Go

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

Затем я случайным образом искал статьи о наследовании в Go и обнаружил, что в некоторых статьях слишком много кода, и формат кода крайне грубый, и нейминг крайне произвольный, похожий на A и B. Люди смотрят на это и забывают, что это такое , Кто кому наследует, я должен вернуться и снова посмотреть на логику.

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

1. Простые комбинации

Говоря о наследовании, все мы знаем, что в Go нетextendsключевое слово, что означает, что в Go нет поддержки наследования на собственном уровне. Вот почему я использовал в начале статьипсевдонаследствоэта фраза. По сути, функция, для реализации которой Go использует интерфейс, называется композицией. Go использует композицию для реализации наследования. Точнее, он использует композицию для замены наследования. Приведу очень простой пример.

1.1 Реализовать родительский класс

Мы используем понятныйживотное-КотВозьмем пример, не будем нести чушь, просто посмотрим код напрямую.

type Animal struct {
	Name string
}

func (a *Animal) Eat() {
	fmt.Printf("%v is eating", a.Name)
	fmt.Println()
}

type Cat struct {
	*Animal
}

cat := &Cat{
	Animal: &Animal{
		Name: "cat",
	},
}
cat.Eat() // cat is eating

1.2 Анализ кода

Во-первых, мы реализовали структуру Animal, представляющую класс животных. И объявите поле Имя, используемое для описания имени животного.

Затем реализуйте метод Eat с Animal в качестве получателя, чтобы описать поведение животных при кормлении.

Наконец, объявляется структура Cat, объединяющая поля Cat. Создайте экземпляр другого кота, вызовите метод Eat, и вы увидите нормальный вывод.

Как видите, сама структура Cat не имеет поля Name и не реализует метод Eat. Единственное, что нужно объединить родительский класс Animal, до сих пор мы доказали, что наследование достигается за счет комбинации.

2. Элегантная композиция

Люди, знакомые с Go, могут воскликнуть следующее, увидев приведенный выше код.

Это слишком грубо -- Лу Синь: Я никогда этого не говорил

На самом деле, вышесказанное предназначено только для тех, кто еще не видел композицию Go. Это прекрасно подходит в качестве простого примера для понимания наследования композиции в Go. Но если его использовать в реальных разработках, этого еще далеко не достаточно.

Например, если я являюсь пользователем этого абстрактного класса, я не могу с первого взгляда понять, что делает этот класс и какие методы я могу вызывать, когда получаю класс Animal. Кроме того, не существует единого метода инициализации, что означает дублирование кода везде, где задействована инициализация. Если есть модификации, связанные с инициализацией на более позднем этапе, то меняется только одна за другой. Итак, далее мы вносим некоторые оптимизации в приведенный выше код.

2.1 Абстрактный интерфейс

Интерфейсы используются для описания поведения класса. Например, интерфейс животного, который мы собираемся абстрагировать, будет описывать поведение животного. Здравый смысл подсказывает нам, что животные могут есть (Есть), издавать звуки (лаять), двигаться (двигаться) и так далее. Вот интересная аналогия.

Интерфейс похож на вывеску, типа Starbucks. Starbucks — это знак (интерфейс).

О чем вы думаете, когда видите этот знак? Американец? Фраппучино? Латте с зеленым чаем? Или латте, или даже стиль оформления магазина.

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

// 模拟动物行为的接口
type IAnimal interface {
	Eat() // 描述吃的行为
}

// 动物 所有动物的父类
type Animal struct {
	Name string
}

// 动物去实现IAnimal中描述的吃的接口
func (a *Animal) Eat() {
	fmt.Printf("%v is eating\n", a.Name)
}

// 动物的构造函数
func newAnimal(name string) *Animal {
	return &Animal{
		Name: name,
	}
}

// 猫的结构体 组合了animal
type Cat struct {
	*Animal
}

// 实现猫的构造函数 初始化animal结构体
func newCat(name string) *Cat {
	return &Cat{
		Animal: newAnimal(name),
	}
}

cat := newCat("cat")
cat.Eat() // cat is eating

На самом деле в Go нет определения конструктора. Например, в Java мы можем использовать конструкторы для инициализации переменных.Для очень простого примера:Integer num = new Integer(1). В Go пользователю необходимо смоделировать реализацию конструктора посредством инициализации структуры.

Затем здесь мы реализуем подкласс Cat и используем метод композиции вместо наследования для вызова методов в Animal. После запуска мы видим, что в структуре Cat нет поля Name и не реализован метод Eat, но он все еще может нормально работать. Это доказывает, что мы добились наследования через композицию.

2.2 Методы переопределения

// 猫结构体IAnimal的Eat方法
func (cat *Cat) Eat() {
	fmt.Printf("children %v is eating\n", cat.Name)
}

cat.Eat()
// children cat is eating

Как видите, структура Cat повторно реализовала метод Eat в Animal, таким образом, переписав его.

2.3 Параметрический полиморфизм

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

В Go это очень удобно.

func check(animal IAnimal) {
	animal.Eat()
}

В этой функции могут обрабатываться все типы юнитов, объединенные с Animal, что соответствует верхнему подстановочному знаку в Java, то есть подстановочному знаку, который может обрабатывать любой конкретный тип и производный класс этого конкретного типа. ? может справиться с этим.

3. Резюме

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

Вот несколько проблем, с которыми, по моему мнению, мы должны столкнуться при оптимизации:

  • милость
  • удобочитаемый
  • представление

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

Или процитируйте предложение, которое я часто писал в своем предыдущем блоге.

Что подходит вам лучше всего

В настоящее время вы можете только выбрать наиболее подходящее решение в соответствии с конкретной ситуацией вашего собственного проекта. Не существует универсального решения.

Делюсь фразой о ядовитом курином бульоне, которую недавно видел, как играю на гитаре, и учусь так же.

На пути к игре на фортепиано нет коротких путей, все обходные пути

Прошлые статьи:

Связанный:

  • Официальная учетная запись WeChat: заметки о полном стеке SH (или прямой поиск WeChat LunhaoHu в интерфейсе добавления официальной учетной записи)