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