После чтения данного урока вы сможете:

  • Создавать структуры с помощью композиции;
  • Встраивать одни методы в другие;
  • Забыть о классическом наследовании.

Если присмотреться, все окружающие вас предметы состоят из мелких деталей. У людей также есть конечности, и на каждой руке пять пальцев. У цветов лепестки и стебли. У марсоходов есть колеса, гусеницы и целые подсистемы вроде Станции Мониторинга Окружающей Среды (REMS). Каждая часть играет свою роль.

Форум Гоферов

Мы работаем над форумом для программистов на Golang. Очень нужны модераторы которые хотят помочь с ответами для новичков и помочь в развитии Go-сообщества.

Go на Форум

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК Go в Telegram

Содержание статьи

В мире объектно-ориентированного программирования объекты также состоят из более мелких объектов. Программисты называют этот композицией объекта или просто композицией.

Разработчики Go используют композицию со структурами. Go предоставляет специальную языковую особенность под названием встраивание для пересылки методов. В данном уроке мы рассмотрим композицию и встраивание на примере выдуманного прогноза погоды от REMS.

Проектирование иерархии может вызвать сложности. В иерархии животного мира звери со схожим поведением делятся на группы. Одни млекопитающие обитают на суше, а другие плавают, но кормлением детенышей занимаются обе группы. Как бы вы определили их организацию? Изменение иерархии может стать чрезвычайно сложным, ведь малейшая модификация может оказать непоправимый ущерб.

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

Плюс ко всему, если вы создадите робота, реализовать поведение ходьбы можно повторно.

Композиция структур в Golang

Отчет о погоде включает разнообразные данные вроде самых низких и высоких температур, указания текущего марсианского дня (его называют сол) и местности. Простым решением будет определение всех необходимых полей в единственной структуре report, как показано в следующем примере:

При разборе Листинга 1 можно заметить, что report является миксом из несопоставимых данных. Он становится более громоздким при включении большего количества информации вроде скорости и направления ветра, давления, влажности, сезона, восхода и заката.

К счастью, можно сгруппировать связанные поля вместе с помощью структур и композиции. В следующем примере определяется структура report, что состоит из структур для температуры и местности.

С определенными типами, отчет о погоде строится из данных о местности и температуры, как показано далее:

Взгляните на Листинг 2. Обратите внимание, здесь сразу понятно, что параметры high и low касаются температур, в то время как те же самые поля из Листинга 1 не столь очевидны.

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

Тип temperature и метод average можно использовать независимо от отчета о погоде, как показано ниже:

При создании отчета о погоде метод average доступен через объединение с полем temperature:

Если вы хотите показать среднюю температуру напрямую через тип report, нет нужды дублировать логику из Листинга 3. Вместо этого напишите метод, что встраивает реальную имплементацию:

С методом для вставки из отчета report в температуру temperature вы получаете удобный доступ к report.average(), все еще структурируя код вокруг более мелких типов.  Далее в уроке мы рассмотрим особенность Go, что позволяет сделать встраивание метода максимально простым.

Вопрос для проверки:

Сравните Листинг 1 и Листинг 2. Какой код удобнее и почему?

Встраивание методов в Golang

Встраивание методов упрощает процесс использования методов. Представьте, что вам нужно узнать погоду на Марсе, зафиксированную модулем Curiosity. Можно перенаправить запрос в систему REMS, что в свою очередь отправит ваш запрос термометру для определения температуры воздуха. Используя встраивание, нет нужды знать путь к методу — просто спросите Curiosity.

Не очень удобно самостоятельно писать методы для вложения из одного типа в другой, как показано в Листинге 3. Такой репетативный код, называемый шаблонным, не приносит ничего, кроме мешанины и беспорядка.

К счастью, Go может встроить метод за вас с помощью внедрения struct. Для внедрения типа в структуру тип уточняется без указания названия поля, как показано в следующем примере:

Все методы типа temperature автоматически делаются доступными через тип report:

Хотя название поля не уточняется, поле все еще существует с тем же названием, что и внедренный тип. Получить доступ к полю temperature можно следующим образом:

Внедрение не только встраивает методы. Поля внутренней структуры доступны из внешней структуры. В дополнение к report.temperature.high вы можете получить доступ к  температуре с помощью report.high, что делается следующим образом:

Как видите, изменения поля report.high отражены в report.temperature.high. Это просто еще один способ получить доступ к тем же данным.

Вы можете внедрить любой тип в структуру, не только сами структуры. В следующем примере у типа sol есть базовый тип int, хотя он внедрен точно так же, как и структуры location и temperature.

К любым методам, объявленным в типе sol, можно получить доступ через поле sol или через тип report:

Вопросы для проверки:

  1. Какие типы можно внедрить в структуру?
  2. Будет ли report.lat правильно отображаться? Если да, то к какому полю он отсылается в Листинге 4?

Столкновение, или коллизия названий в Go

Отчет о погоде в порядке. Теперь нужно выяснить, сколько дней потребуется марсоходу для поездки от одной местности к другой. Марсоход Curiosity передвигается приблизительно на скорости 200 метров в день. По этой причине требуется добавить метод days типу местности для вычислений, как показано в следующем примере:

Структура report внедряет sol и location, оба типа с методом под названием days.

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

Если используется метод days над типом report, компилятор Go не знает, нужно ли перенаправить вызов методу для sol или методу для location. В результате выведется ошибка:

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

Есть ли наследование в Golang?

Классические языки программирования вроде C++, Java, PHP, Python, Ruby или Swift могут использовать композицию, а также задействуют такую особенность языка как наследование.

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

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

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

Отдавайте предпочтение композиции, а не классовому наследованию.

«Банда четырех»,
Приемы объектно-ориентированного проектирования.
Паттерны проектирования

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

Приемник average() из Листинга 3 всегда типа temperature, даже когда встраивается через report. С делегированием или наследованием приемник мог бы стать типом report, однако в Go нет ни делегирования, ни наследования. Ничего страшного, ведь здесь оно и не нужно:

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

Санди Мец,
Практичный объектно-ориентированный дизайн в Ruby

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

Вопрос для проверки:

Если внедренные типы реализуют метод с тем же названием, сообщит ли компилятор Go об ошибке?

Заключение

  • Композиция является техникой для разделения крупных структур на более мелкие и их последующего объединения;
  • Внедрение дает доступ к полям внутренних структур внешней структуры;
  • Методы автоматически встраиваются, когда вы внедряете типы в структуру;
  • Go сообщит о коллизии названий, вызванной внедрением, но только в том случае, если эти методы используются.

Итоговое задание для проверки:

Напишите программу со структурой gps для Системы Глобального Позиционирования (GPS). Данная структура должна состоять из текущей местности, места назначения и мира.

Реализация метода description для типа location возвращает строку, содержащую название, широту и долготу. Тип world должен имплементировать метод для расстояния, используемый в уроке о структурах и методах.

Прикрепите два метода к типу gps. Для начала прикрепите метод distance, что находит расстояние между текущей местностью и местом назначения. Затем реализуйте метод message, что возвращает строку с оставшимися километрами до пункта назначения.

Финальным шагом станет создание структуры rover, что внедряет gps и написание функции main для тестирования всего созданного. Инициализируйте GPS для Марса с текущей локацией Bradbury Landing (-4.5895, 137.4417) и пунктом назначения Elysium Planitia (4.5, 135.9). Затем создайте элемент curiosity для марсохода и выведите его message (что встраивается в gps).

Понравилась статья?

Поддержи наш проект, чтобы мы могли создать больше хорошего контента!