Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Дисклаймер
Статья имеет единственную цель - изучение аспектов языка 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 нужный метод.
Вроде все, что хотел показать.
Всем спасибо, кто прочел.