Как стать автором
Обновить

Наследование методов в GO отсутствует? А вот и нет

Время на прочтение4 мин
Количество просмотров11K

Дисклаймер

Статья имеет единственную цель - изучение аспектов языка GO. Жду нескончаемый поток критики и готов к ней.

Предисловие

Недавно начал изучать GO. По моему мнению: концепция полиморфизма в GO достаточно элегантная. "Путь упрощения ради эффективности" - это мой путь.

Итак, в процессе изучения я "придумал" себе задачу, достаточно нагромождённую, что бы попробовать все, что предлагает GO. И вот на одном из этапов, я спроектировал композицию у которой импортируемая (старшая) структура реализует некий интерфейс и мне понадобилось, что бы принимаемая композицию структура "переопределила" один из методов интерфейса старшей структуры. И как оказалось это возможно! Несмотря на отсутствие в языке типового полиморфизма.

Пример

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

Итак, в чем идея?

Вот что имеем изначально:

package main

import "fmt"

//Speaker - интерфейс говоруна
type Speaker interface {
	toSpeak() //метод говоруна - говорить
}

//speaker - структура описывающая говоруна
type speaker struct {
	message      string //что нужно сказать хранится здесь
}
//Реализация интерфейса 
func (s speaker) toSpeak() {
	fmt.Println(s.message)
}
//NewSpeaker -"конструктор" говоруна 
func NewSpeaker(message string) Speaker {
	var result speaker = speaker{
		message: message, //Передаем "что сказать" полю структуры
	}
	return result
}
//Запускаем говоруна
func main() {
	var s Speaker = NewSpeaker("говорю")
	s.toSpeak()
}

Вывод:

говорю

Тут у нас есть Говорун ( структура speaker), который "научился" говорить, реализовав метод интерфейса Speaker.toSpeak().

В main мы создаем говоруна через экземпляр интерфейса, что бы показать, что все у нас полиморфно и может использоваться в коде любого сервера, который умеет обращаться с интерфейсом Speaker ( и не зависеть от частных реализаций этого интерфейса).

Все просто.

Далее на сцену выходит новая структура: Пересмешник - repeater.

type repeater struct {  
  speaker   
  howManyTimesToRepeat int
}

По сценарию, его задача сказать message столько раз, сколько записано в его поле howManyTimesToRepeat, и при этом он имеет встраиваемую композицию от speaker'а.

Вопрос: как переопределить поведение метода интерфейса Speaker.toSpeak() для экземпляров структуры repeater, при этом не реализуя данный интерфейс для структуры repeater ( тем более, что это будет теневой реализацией - перекрытием методов) ?

Ответ, который я хочу показать, заключается в использовании в качестве типа одного из полей структуры speaker функции. GO позволяет хранить функции в структуре!

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

И так вот, что получается в итоге:

package main

import "fmt"

//Speaker - интерфейс говоруна
type Speaker interface {
	toSpeak() //метод говоруна - говорить
}

//speaker - структура описывающая говоруна
type speaker struct {
	message      string
	callBackFunc func() //То самое новое поле хранящее функцию
}

//toSpeak - Реализация интерфейса - !ЭТО ВАЖНОЕ МЕСТО!
//теперь реализация метода интерфеса имеет полиморфное поведение!
func (s speaker) toSpeak() {
	//fmt.Println(s.message) <- вот так было раньше
	s.callBackFunc() //<- теперь здесь будет вызов той функции, что была сохранена в поле структуры!
}

//howToSpeak - локальная (закрытая) реализация "как говорить" говоруну
func (s speaker) howToSpeakSpeaker() {
	fmt.Println(s.message)
}

//NewSpeaker -"конструктор" говоруна
func NewSpeaker(message string) Speaker {
	var result speaker = speaker{
		message: message, //Передаем "что сказать" полю структуры
	}
	//!!ЭТО ВАЖНОЕ МЕСТО!
	//Назначаем полю частную реализацию метода "как говорить" говоруну
	result.callBackFunc = result.howToSpeakSpeaker
	return result
}

//repeater - это пересмешник
type repeater struct {
	speaker //композиция говоруна в структуре пересмешника
	howManyTimesToRepeat int
}

//howToSpeak - локальная (закрытая) реализация "как говорить" пересмешнику
func (r repeater) howToSpeakRepeater() {
	fmt.Println(r.message) //Здесь он просто говорит
	for i := 1; i < r.howManyTimesToRepeat; i++ {
		//А вот здесь он уже передразнивает - повторяет много раз
		fmt.Println(r.message)
	}
}

//NewSpeaker -"конструктор" пересмешника
func NewRepeater(message string, count int) Speaker {
	var result repeater = repeater{
		speaker: speaker{
			message: message,
		},
		howManyTimesToRepeat: count,
	}
	//Назначаем полю - как должен говорит пересмешник
	result.callBackFunc = result.howToSpeakRepeater
	return result
}

func main() {
	var s Speaker = NewSpeaker("говорю")
	s.toSpeak()

	var r Speaker = NewRepeater("говорю как пересмешник три раза", 3)
	r.toSpeak()

}

Вывод:

говорю
говорю как пересмешник три раза
говорю как пересмешник три раза
говорю как пересмешник три раза

Что же произошло и как это работает?

Прежде всего обратите внимание на новое поле callBackFunc func() структуры speaker ( строка 13) . В этом поле мы будем хранить ссылку на частный метод реализующий "говорение". Для говоруна это будет один метод, для пересмешника другой.

Далее: изменилась реализация метода интерфейса ( строка 20). Теперь вместо статической реализации у метода полиморфное поведение: будет вызван тот метод, который хранится в поле callBackFunc конкретного экземпляра структуры.

Ну остальное, я думаю, и так ясно: У каждой структуры есть своя реализация метода говорения ( howToSpeakSpeaker и howToSpeakRepeater) и в конструкторе каждой структуры назначается в поле callBackFunc нужный метод.

Вроде все, что хотел показать.

Всем спасибо, кто прочел.

Теги:
Хабы:
Всего голосов 10: ↑4 и ↓60
Комментарии15

Публикации

Истории

Работа

Go разработчик
81 вакансия

Ближайшие события